[
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 7\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - bug\n  - enhancement\n  - feature\n  - documentation\n  - build stability\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  activity in the last seven days. It will be closed if no further activity\n  occurs within the next seven days. Thank you for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\nunmarkComment: false\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Android CI\n\non:\n    push:\n    pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout project\n        uses: actions/checkout@v4\n\n      - name: Setup Java\n        uses: actions/setup-java@v4\n        with:\n          distribution: \"zulu\"\n          java-version: \"17\"\n\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v4\n\n      - name: Run Gradle Build\n        run: |\n          ./gradlew build \\\n            -x :library:test:testDebugUnitTest \\\n            :library:test:assembleDebugUnitTest \\\n            -x :library:testDebugUnitTest \\\n            :library:assembleDebugUnitTest \\\n            -x :annotation:ksp:test:testDebugUnitTest \\\n            :annotation:ksp:test:assembleDebugUnitTest \\\n            -x :third_party:disklrucache:testDebugUnitTest \\\n            :third_party:disklrucache:assembleDebugUnitTest \\\n            -x :integration:cronet:testDebugUnitTest \\\n            :integration:cronet:assembleDebugUnitTest \\\n            -x :integration:gifencoder:testDebugUnitTest \\\n            :integration:gifencoder:assembleDebugUnitTest \\\n            -x :integration:ktx:testDebugUnitTest \\\n            :integration:ktx:assembleDebugUnitTest \\\n            -x :integration:concurrent:testDebugUnitTest \\\n            :integration:concurrent:assembleDebugUnitTest \\\n            -x :integration:volley:testDebugUnitTest \\\n            :integration:volley:assembleDebugUnitTest \\\n            -x :integration:sqljournaldiskcache:testDebugUnitTest \\\n            :integration:sqljournaldiskcache:assembleDebugUnitTest \\\n            -x :third_party:gif_decoder:testDebugUnitTest \\\n            :third_party:gif_decoder:assembleDebugUnitTest \\\n            :samples:flickr:build \\\n            :samples:giphy:build \\\n            :samples:contacturi:build \\\n            :samples:gallery:build \\\n            :samples:imgur:build \\\n            :samples:svg:build \\\n            :instrumentation:assembleAndroidTest \\\n            :benchmark:assembleAndroidTest \\\n            :glide:releaseJavadoc \\\n            :annotation:ksp:test:test \\\n            :integration:ktx:apiCheck \\\n            :annotation:ksp:integrationtest:test \\\n            --parallel\n"
  },
  {
    "path": ".github/workflows/publish-manual.yml",
    "content": "name: Publish to Maven (manual)\n\non:\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout project\n        uses: actions/checkout@v4\n      - name: Make Gradle wrapper executable\n        run: chmod +x ./gradlew\n      - uses: actions/setup-java@v4\n        with:\n          distribution: \"zulu\"\n          java-version: \"17\"\n      - name: Build and publish everything to Maven Central\n      # This can be improved in Gradle\n        run: ./gradlew :mocks:publishToMavenCentral :annotation:publishToMavenCentral :annotation:compiler:publishToMavenCentral :library:publishToMavenCentral :integration:sqljournaldiskcache:publishToMavenCentral :annotation:ksp:publishToMavenCentral :integration:recyclerview:publishToMavenCentral :integration:avif:publishToMavenCentral :integration:okhttp:publishToMavenCentral :integration:gifencoder:publishToMavenCentral :integration:ktx:publishToMavenCentral :integration:okhttp4:publishToMavenCentral :integration:volley:publishToMavenCentral :integration:concurrent:publishToMavenCentral :integration:cronet:publishToMavenCentral :integration:okhttp3:publishToMavenCentral :integration:compose:publishToMavenCentral :third_party:disklrucache:publishToMavenCentral :third_party:gif_decoder:publishToMavenCentral\n        env:\n          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}\n          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}\n          ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_SIGNING_KEY_ID }}\n          ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_SIGNING_PRIVATE_KEY }}\n          ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_SIGNING_PRIVATE_KEY_PASSWORD }}\n          ORG_GRADLE_PROJECT_mavenCentralPublishing: true\n          ORG_GRADLE_PROJECT_mavenCentralAutomaticPublishing: false\n"
  },
  {
    "path": ".gitignore",
    "content": "# Android\nlocal.properties\n*.keystore\n*.DS_Store\n\n# Gradle\n.gradle\nbuild\njacoco.exec\n\n# gh-pages\ndoc/**\n_site/*\n_pages/*\ndocs/**/*\n\n# Vim\n*.swp\n*.swo\n\n# sed\n*.bak\n\n# Intellij\n*.iml\n*.ipr\n*.iws\n.idea/**\n!.idea/codeStyleSettings.xml\n!.idea/inspectionProfiles\n!.idea/inspectionProfiles/Project_Default.xml\n\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "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=\"OTHER_INDENT_OPTIONS\">\n          <value>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n            <option name=\"USE_TAB_CHARACTER\" value=\"false\" />\n            <option name=\"SMART_TABS\" value=\"false\" />\n            <option name=\"LABEL_INDENT_SIZE\" value=\"0\" />\n            <option name=\"LABEL_INDENT_ABSOLUTE\" value=\"false\" />\n            <option name=\"USE_RELATIVE_INDENTS\" value=\"false\" />\n          </value>\n        </option>\n        <option name=\"INSERT_INNER_CLASS_IMPORTS\" value=\"true\" />\n        <option name=\"CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\" />\n        <option name=\"NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\" />\n        <option name=\"PACKAGES_TO_USE_IMPORT_ON_DEMAND\">\n          <value />\n        </option>\n        <option name=\"IMPORT_LAYOUT_TABLE\">\n          <value>\n            <package name=\"\" withSubpackages=\"true\" static=\"true\" />\n            <emptyLine />\n            <package name=\"\" withSubpackages=\"true\" static=\"false\" />\n          </value>\n        </option>\n        <option name=\"RIGHT_MARGIN\" value=\"100\" />\n        <option name=\"JD_P_AT_EMPTY_LINES\" value=\"false\" />\n        <option name=\"JD_KEEP_EMPTY_PARAMETER\" value=\"false\" />\n        <option name=\"JD_KEEP_EMPTY_EXCEPTION\" value=\"false\" />\n        <option name=\"JD_KEEP_EMPTY_RETURN\" value=\"false\" />\n        <option name=\"KEEP_CONTROL_STATEMENT_IN_ONE_LINE\" value=\"false\" />\n        <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\" />\n        <option name=\"KEEP_BLANK_LINES_BEFORE_RBRACE\" value=\"0\" />\n        <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\" />\n        <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\" />\n        <option name=\"SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE\" value=\"true\" />\n        <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\" />\n        <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\" />\n        <option name=\"EXTENDS_LIST_WRAP\" value=\"1\" />\n        <option name=\"THROWS_KEYWORD_WRAP\" value=\"1\" />\n        <option name=\"METHOD_CALL_CHAIN_WRAP\" value=\"1\" />\n        <option name=\"BINARY_OPERATION_WRAP\" value=\"1\" />\n        <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\" />\n        <option name=\"TERNARY_OPERATION_WRAP\" value=\"1\" />\n        <option name=\"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE\" value=\"true\" />\n        <option name=\"FOR_STATEMENT_WRAP\" value=\"1\" />\n        <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\" />\n        <option name=\"WRAP_COMMENTS\" value=\"true\" />\n        <option name=\"IF_BRACE_FORCE\" value=\"3\" />\n        <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\" />\n        <option name=\"WHILE_BRACE_FORCE\" value=\"3\" />\n        <option name=\"FOR_BRACE_FORCE\" value=\"3\" />\n        <AndroidXmlCodeStyleSettings>\n          <option name=\"USE_CUSTOM_SETTINGS\" value=\"true\" />\n          <option name=\"LAYOUT_SETTINGS\">\n            <value>\n              <option name=\"INSERT_BLANK_LINE_BEFORE_TAG\" value=\"false\" />\n            </value>\n          </option>\n        </AndroidXmlCodeStyleSettings>\n        <JSCodeStyleSettings>\n          <option name=\"INDENT_CHAINED_CALLS\" value=\"false\" />\n        </JSCodeStyleSettings>\n        <JavaCodeStyleSettings>\n          <option name=\"DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION\" value=\"true\" />\n        </JavaCodeStyleSettings>\n        <Python>\n          <option name=\"USE_CONTINUATION_INDENT_FOR_ARGUMENTS\" value=\"true\" />\n        </Python>\n        <TypeScriptCodeStyleSettings>\n          <option name=\"INDENT_CHAINED_CALLS\" value=\"false\" />\n        </TypeScriptCodeStyleSettings>\n        <XML>\n          <option name=\"XML_ALIGN_ATTRIBUTES\" value=\"false\" />\n          <option name=\"XML_LEGACY_SETTINGS_IMPORTED\" value=\"true\" />\n        </XML>\n        <codeStyleSettings language=\"CSS\">\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"ECMA Script Level 4\">\n          <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\" />\n          <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\" />\n          <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\" />\n          <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\" />\n          <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\" />\n          <option name=\"EXTENDS_LIST_WRAP\" value=\"1\" />\n          <option name=\"BINARY_OPERATION_WRAP\" value=\"1\" />\n          <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\" />\n          <option name=\"TERNARY_OPERATION_WRAP\" value=\"1\" />\n          <option name=\"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE\" value=\"true\" />\n          <option name=\"FOR_STATEMENT_WRAP\" value=\"1\" />\n          <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\" />\n          <option name=\"IF_BRACE_FORCE\" value=\"3\" />\n          <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\" />\n          <option name=\"WHILE_BRACE_FORCE\" value=\"3\" />\n          <option name=\"FOR_BRACE_FORCE\" value=\"3\" />\n          <option name=\"PARENT_SETTINGS_INSTALLED\" value=\"true\" />\n        </codeStyleSettings>\n        <codeStyleSettings language=\"HTML\">\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"JAVA\">\n          <option name=\"KEEP_CONTROL_STATEMENT_IN_ONE_LINE\" value=\"false\" />\n          <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\" />\n          <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\" />\n          <option name=\"ALIGN_MULTILINE_RESOURCES\" value=\"false\" />\n          <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\" />\n          <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\" />\n          <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\" />\n          <option name=\"EXTENDS_LIST_WRAP\" value=\"1\" />\n          <option name=\"THROWS_KEYWORD_WRAP\" value=\"1\" />\n          <option name=\"METHOD_CALL_CHAIN_WRAP\" value=\"1\" />\n          <option name=\"BINARY_OPERATION_WRAP\" value=\"1\" />\n          <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\" />\n          <option name=\"TERNARY_OPERATION_WRAP\" value=\"1\" />\n          <option name=\"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE\" value=\"true\" />\n          <option name=\"KEEP_SIMPLE_BLOCKS_IN_ONE_LINE\" value=\"true\" />\n          <option name=\"KEEP_SIMPLE_METHODS_IN_ONE_LINE\" value=\"true\" />\n          <option name=\"KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE\" value=\"true\" />\n          <option name=\"KEEP_SIMPLE_CLASSES_IN_ONE_LINE\" value=\"true\" />\n          <option name=\"FOR_STATEMENT_WRAP\" value=\"1\" />\n          <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\" />\n          <option name=\"IF_BRACE_FORCE\" value=\"3\" />\n          <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\" />\n          <option name=\"WHILE_BRACE_FORCE\" value=\"3\" />\n          <option name=\"FOR_BRACE_FORCE\" value=\"3\" />\n          <option name=\"PARENT_SETTINGS_INSTALLED\" value=\"true\" />\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"JSON\">\n          <indentOptions>\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"JavaScript\">\n          <option name=\"RIGHT_MARGIN\" value=\"80\" />\n          <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\" />\n          <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\" />\n          <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\" />\n          <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\" />\n          <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\" />\n          <option name=\"BINARY_OPERATION_WRAP\" value=\"1\" />\n          <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\" />\n          <option name=\"TERNARY_OPERATION_WRAP\" value=\"1\" />\n          <option name=\"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE\" value=\"true\" />\n          <option name=\"FOR_STATEMENT_WRAP\" value=\"1\" />\n          <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\" />\n          <option name=\"IF_BRACE_FORCE\" value=\"3\" />\n          <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\" />\n          <option name=\"WHILE_BRACE_FORCE\" value=\"3\" />\n          <option name=\"FOR_BRACE_FORCE\" value=\"3\" />\n          <option name=\"PARENT_SETTINGS_INSTALLED\" value=\"true\" />\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"ObjectiveC\">\n          <option name=\"KEEP_BLANK_LINES_BEFORE_RBRACE\" value=\"1\" />\n          <option name=\"BLANK_LINES_BEFORE_IMPORTS\" value=\"0\" />\n          <option name=\"BLANK_LINES_AFTER_IMPORTS\" value=\"0\" />\n          <option name=\"BLANK_LINES_AROUND_CLASS\" value=\"0\" />\n          <option name=\"BLANK_LINES_AROUND_METHOD\" value=\"0\" />\n          <option name=\"BLANK_LINES_AROUND_METHOD_IN_INTERFACE\" value=\"0\" />\n          <option name=\"ALIGN_MULTILINE_BINARY_OPERATION\" value=\"false\" />\n          <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\" />\n          <option name=\"FOR_STATEMENT_WRAP\" value=\"1\" />\n          <option name=\"ASSIGNMENT_WRAP\" value=\"1\" />\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"PROTO\">\n          <option name=\"RIGHT_MARGIN\" value=\"80\" />\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"Python\">\n          <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\" />\n          <option name=\"RIGHT_MARGIN\" value=\"80\" />\n          <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\" />\n          <option name=\"PARENT_SETTINGS_INSTALLED\" value=\"true\" />\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"SASS\">\n          <indentOptions>\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"SCSS\">\n          <indentOptions>\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"TypeScript\">\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"XML\">\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n          <arrangement>\n            <rules>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>xmlns:android</NAME>\n                      <XML_ATTRIBUTE />\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_ATTRIBUTE />\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_ATTRIBUTE />\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>style</NAME>\n                      <XML_ATTRIBUTE />\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_ATTRIBUTE />\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>.*:.*Style</NAME>\n                      <XML_ATTRIBUTE />\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>.*:layout_width</NAME>\n                      <XML_ATTRIBUTE />\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_ATTRIBUTE />\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_weight</NAME>\n                      <XML_ATTRIBUTE />\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_margin</NAME>\n                      <XML_ATTRIBUTE />\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_marginTop</NAME>\n                      <XML_ATTRIBUTE />\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_marginBottom</NAME>\n                      <XML_ATTRIBUTE />\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_marginStart</NAME>\n                      <XML_ATTRIBUTE />\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_marginEnd</NAME>\n                      <XML_ATTRIBUTE />\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_marginLeft</NAME>\n                      <XML_ATTRIBUTE />\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_marginRight</NAME>\n                      <XML_ATTRIBUTE />\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_ATTRIBUTE />\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>.*:padding</NAME>\n                      <XML_ATTRIBUTE />\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>.*:paddingTop</NAME>\n                      <XML_ATTRIBUTE />\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>.*:paddingBottom</NAME>\n                      <XML_ATTRIBUTE />\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>.*:paddingStart</NAME>\n                      <XML_ATTRIBUTE />\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>.*:paddingEnd</NAME>\n                      <XML_ATTRIBUTE />\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>.*:paddingLeft</NAME>\n                      <XML_ATTRIBUTE />\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>.*:paddingRight</NAME>\n                      <XML_ATTRIBUTE />\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>\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-auto</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/tools</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        <codeStyleSettings language=\"protobuf\">\n          <option name=\"RIGHT_MARGIN\" value=\"80\" />\n          <indentOptions>\n            <option name=\"INDENT_SIZE\" value=\"2\" />\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\" />\n            <option name=\"TAB_SIZE\" value=\"2\" />\n          </indentOptions>\n        </codeStyleSettings>\n      </value>\n    </option>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n  </component>\n</project>\n"
  },
  {
    "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=\"SerializableHasSerialVersionUIDField\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\">\n      <option name=\"ignoreAnonymousInnerClasses\" value=\"false\" />\n      <option name=\"superClassString\" value=\"\" />\n    </inspection_tool>\n  </profile>\n</component>"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\nContributions of all types are welcome.\nWe use GitHub as our bug and feature tracker both for code and for other aspects of the library (documentation, the wiki, etc.).\n\n\n## Asking Questions\nThe best way to ask general questions is to send an email to our [mailing list][2], or join [#glide-library on freenode.org][3].\n\n\n## Filing issues\nWhen in doubt, file an issue. We'd rather close a few duplicate issues than let a problem go unnoticed.\nSimilarly if you support a particular feature request, feel free to let us know by commenting on the issue or [subscribing][6] to the issue.\n\nTo file a new issue, please use our issue template and fill out the template as much as possible (remove irrelevant parts).\nThe more information you can provide, the more likely we are to be able help.\n\n\n## Contributing code\nPull requests are welcome for all parts of the codebase, especially the integration libraries.\nYou can find instructions on building the project in [README.md][5].\nOur code style is defined in Intellij project files in the repo and also by our Checkstyle config.\nIf you'd like to submit code, but can't get the style checks to pass, feel free to put up your pull request anyway and we can help you fix the style issues.\nIf you'd like to contribute code, you will need to sign [Google's individual contributor license agreement][4] which will be asked when you create the PR by [googlebot](https://github.com/googlebot) should you forget it.\n\n## Labels\nLabels on issues are managed by contributors, you don't have to worry about them. Here's a list of what they mean:\n\n * **bug**: feature that should work, but doesn't\n * **enhancement**: minor tweak/addition to existing behavior\n * **feature**: new behavior, bigger than enhancement, it gives more bang to Glide\n * **question**: no need to modify Glide to fix the issue, usually a usage problem\n * **reproducible**: has enough information to very easily reproduce, mostly in form of a small project in a GitHub repo\n * **repro-needed**: we need some code to be able to reproduce and debug locally, otherwise there's not much we can do\n * **duplicate**: there's another issue which already covers/tracks this\n * **wontfix**: working as intended, or won't be fixed due to compatibility or other reasons\n * **invalid**: there isn't enough information to make a verdict, or unrelated to Glide\n * **non-library**: issue is not in the core library code, but rather in documentation, samples, build process, releases\n * **v4**: problem originated in v4, or question about v4 (while v3 is in wide use)\n\n*bug + enhancement: feature that doesn't work, but it's an edge case that either has a workaround or doesn't affect many users*\n\n\n[1]: https://github.com/bumptech/glide/issues/new?body=**Glide%20Version**%3A%0A**Integration%20libraries**%3A%0A**Device/Android%20Version**%3A%0A**Issue%20details%20/%20Repro%20steps%20/%20Use%20case%20background**%3A%0A%0A**Glide%20load%20line**%3A%0A%60%60%60java%0AGlide.with%28...%29.....load%28...%29.....into%28...%29%3B%0A%60%60%60%0A%0A**Layout%20XML**%3A%0A%60%60%60xml%0A%3C...Layout%3E%0A%20%20%20%20%3CImageView%20android%3AscaleType%3D%22...%22%20...%20/%3E%0A%3C/..Layout%3E%0A%60%60%60%0A%0A**Stack%20trace%20/%20LogCat**%3A%0A%60%60%60ruby%0Apaste%20stack%20trace%20here%0A%60%60%60\n[2]: https://groups.google.com/forum/#!forum/glidelibrary\n[3]: http://webchat.freenode.net/?channels=glide-library\n[4]: https://developers.google.com/open-source/cla/individual\n[5]: https://github.com/bumptech/glide\n[6]: https://help.github.com/articles/subscribing-to-conversations/\n"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "content": "<!--\nPlease fill in the below fields with some data to help us best diagnose the issue.\nThe more specific you are, the better! You can help a lot by not making us ask these questions.\nFeel free to remove any irrelevant parts that you know are not related to the issue.\nAny HTML comment like this will be stripped when rendering markdown, no need to delete them.\n-->\n\n\n<!-- What version of Glide you're running, for example: 3.7.1 | 3.8.0-SNAPSHOT | 4.0.0-SNAPSHOT\nIt's essentially the version number from your build.gradle: `dependencies { compile '...:x.y.z' }` -->\n**Glide Version**:\n\n<!-- Do you use any integration library, like OkHttp3 or Volley? For example:\nFails to display with stock networking, but works with okhttp3-1.4.0 -->\n**Integration libraries**:\n\n<!-- What devices you managed to get the issue to come up on? For example:\nfails on Galaxy S4/GT-I9500 4.4.2, works fine on Nexus 6P 5.1 and Genymotion Nexus 5 5.0.1 -->\n**Device/Android Version**:\n\n<!-- Share the details of your issue in prose, detailing actual and expected behavior. It also helps if you give some info **why** you are trying to do something as opposed to **what** is not working. -->\n**Issue details / Repro steps / Use case background**: \n\n<!-- How do you use Glide?\nMake sure you include everything as is in your app's code:\nChanging a single method parameter can yield totally different results.\nPlease clarify any magic variables that appear in the code, for example: \"// `this` is a Fragment\"\n-->\n**Glide load line / `GlideModule` (if any) / list Adapter code (if any)**:\n```java\nGlide.with...\n```\n\n<!-- How does your app look like?\nWe're most interested in the layout attributes and the hierarchy around the ImageView -->\n**Layout XML**:\n```xml\n<FrameLayout xmlns:android=\"...\n```\n\n<!--\nWhat is the error message that you got in the log?\nYou can find some help on diagnosing issues here: https://github.com/bumptech/glide/wiki/Debugging-and-Error-Handling\n-->\n**Stack trace / LogCat**:\n```ruby\npaste stack trace and/or log here\n```\n\n<!-- Bonus points if you attach a relevant screenshot, screen recording or a small demo project -->\n"
  },
  {
    "path": "LICENSE",
    "content": "License for everything not in third_party and not otherwise marked:\n\nCopyright 2014 Google, Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are\npermitted provided that the following conditions are met:\n\n   1. Redistributions of source code must retain the above copyright notice, this list of\n         conditions and the following disclaimer.\n\n   2. Redistributions in binary form must reproduce the above copyright notice, this list\n         of conditions and the following disclaimer in the documentation and/or other materials\n         provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED\nWARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe views and conclusions contained in the software and documentation are those of the\nauthors and should not be interpreted as representing official policies, either expressed\nor implied, of Google, Inc.\n---------------------------------------------------------------------------------------------\nLicense for third_party/disklrucache:\n\nCopyright 2012 Jake Wharton\nCopyright 2011 The Android Open Source Project\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---------------------------------------------------------------------------------------------\nLicense for third_party/gif_decoder:\n\nCopyright (c) 2013 Xcellent Creations, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n---------------------------------------------------------------------------------------------\nLicense for third_party/gif_encoder/AnimatedGifEncoder.java and\nthird_party/gif_encoder/LZWEncoder.java:\n\nNo copyright asserted on the source code of this class. May be used for any\npurpose, however, refer to the Unisys LZW patent for restrictions on use of\nthe associated LZWEncoder class. Please forward any corrections to\nkweiner@fmsware.com.\n\n-----------------------------------------------------------------------------\nLicense for third_party/gif_encoder/NeuQuant.java\n\nCopyright (c) 1994 Anthony Dekker\n\nNEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See\n\"Kohonen neural networks for optimal colour quantization\" in \"Network:\nComputation in Neural Systems\" Vol. 5 (1994) pp 351-367. for a discussion of\nthe algorithm.\n\nAny party obtaining a copy of these files from the author, directly or\nindirectly, is granted, free of charge, a full and unrestricted irrevocable,\nworld-wide, paid up, royalty-free, nonexclusive right and license to deal in\nthis software and documentation files (the \"Software\"), including without\nlimitation the rights to use, copy, modify, merge, publish, distribute,\nsublicense, and/or sell copies of the Software, and to permit persons who\nreceive copies from any such party to do so, with the only requirement being\nthat this copyright notice remain intact.\n"
  },
  {
    "path": "PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Make sure you've run `gradlew clean check jar assemble` before commit. -->\n<!-- Don't forget that you can always force push to your private branches to make changes. -->\n<!-- Please make sure there are no weird commits in the change set by rebasing to latest upstream. -->\n<!-- Please squash typo/checkstyle/review fix commits into the base commit. -->\n\n## Description\n<!-- Please describe the changes you made on a high level. -->\n<!-- Make sure you reference the GitHub issue here if this change is related to one. -->\n\n## Motivation and Context\n<!-- Why is this change required? What problem does it solve? -->\n<!-- If it's fixing a bug reference it or provide repro steps. -->\n\n<!-- If you have any issues feel free to create the PR anyway, we'll help to resolve them. -->"
  },
  {
    "path": "README.md",
    "content": "Glide\n=====\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.bumptech.glide/glide/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.bumptech.glide/glide)\n| [View Glide's documentation][20] | [简体中文文档][22] | [Report an issue with Glide][5]\n\nGlide is a fast and efficient open source media management and image loading framework for Android that wraps media\ndecoding, memory and disk caching, and resource pooling into a simple and easy to use interface.\n\n![](static/glide_logo.png)\n\nGlide supports fetching, decoding, and displaying video stills, images, and animated GIFs. Glide includes a flexible API\nthat allows developers to plug in to almost any network stack. By default Glide uses a custom `HttpUrlConnection` based\nstack, but also includes utility libraries plug in to Google's Volley project or Square's OkHttp library instead.\n\nGlide's primary focus is on making scrolling any kind of a list of images as smooth and fast as possible, but Glide is\nalso effective for almost any case where you need to fetch, resize, and display a remote image.\n\nDownload\n--------\nFor detailed instructions and requirements, see Glide's [download and setup docs page][28].\n\nYou can download a jar from GitHub's [releases page][1].\n\nOr use Gradle:\n\n```gradle\nrepositories {\n  google()\n  mavenCentral()\n}\n\ndependencies {\n  implementation 'com.github.bumptech.glide:glide:5.0.5'\n}\n```\n\nOr Maven:\n\n```xml\n<dependency>\n  <groupId>com.github.bumptech.glide</groupId>\n  <artifactId>glide</artifactId>\n  <version>5.0.5</version>\n</dependency>\n```\n\nFor info on using the bleeding edge, see the [Snapshots][17] docs page.\n\nR8 / Proguard\n--------\nThe specific rules are [already bundled](library/proguard-rules.txt) into the aar which can be interpreted by R8 automatically\n\nHow do I use Glide?\n-------------------\nCheck out the [documentation][20] for pages on a variety of topics, and see the [javadocs][3].\n\nFor Glide v3, see the [wiki][2].\n\nSimple use cases will look something like this:\n\n```java\n// For a simple view:\n@Override public void onCreate(Bundle savedInstanceState) {\n  ...\n  ImageView imageView = (ImageView) findViewById(R.id.my_image_view);\n\n  Glide.with(this).load(\"https://goo.gl/gEgYUd\").into(imageView);\n}\n\n// For a simple image list:\n@Override public View getView(int position, View recycled, ViewGroup container) {\n  final ImageView myImageView;\n  if (recycled == null) {\n    myImageView = (ImageView) inflater.inflate(R.layout.my_image_view, container, false);\n  } else {\n    myImageView = (ImageView) recycled;\n  }\n\n  String url = myUrls.get(position);\n\n  Glide\n    .with(myFragment)\n    .load(url)\n    .centerCrop()\n    .placeholder(R.drawable.loading_spinner)\n    .into(myImageView);\n\n  return myImageView;\n}\n```\n\nStatus\n------\nVersion 4 is now released and stable. Updates are released periodically with new features and bug fixes.\n\nComments/bugs/questions/pull requests are always welcome! Please read [CONTRIBUTING.md][5] on how to report issues.\n\nCompatibility\n-------------\n\n * **Minimum Android SDK**: Glide v4 requires a minimum API level of 14.\n * **Compile Android SDK**: Glide v4 requires you to compile against API 26 or later.\n\n If you need to support older versions of Android, consider staying on [Glide v3][14], which works on API 10, but is not actively maintained.\n\n * **OkHttp 3.x**: There is an optional dependency available called `okhttp3-integration`, see the [docs page][23].\n * **Volley**: There is an optional dependency available called `volley-integration`, see the [docs page][24].\n * **Round Pictures**: `CircleImageView`/`CircularImageView`/`RoundedImageView` are known to have [issues][18] with `TransitionDrawable` (`.crossFade()` with `.thumbnail()` or `.placeholder()`) and animated GIFs, use a [`BitmapTransformation`][19] (`.circleCrop()` will be available in v4) or `.dontAnimate()` to fix the issue.\n * **Huge Images** (maps, comic strips): Glide can load huge images by downsampling them, but does not support zooming and panning `ImageView`s as they require special resource optimizations (such as tiling) to work without `OutOfMemoryError`s.\n\nBuild\n-----\nBuilding Glide with gradle is fairly straight forward:\n\n```shell\ngit clone https://github.com/bumptech/glide.git\ncd glide\n./gradlew jar\n```\n\n**Note**: Make sure your *Android SDK* has the *Android Support Repository* installed, and that your `$ANDROID_HOME` environment\nvariable is pointing at the SDK or add a `local.properties` file in the root project with a `sdk.dir=...` line.\n\nSamples\n-------\nFollow the steps in the [Build](#build) section to set up the project and then:\n\n```shell\n./gradlew :samples:flickr:run\n./gradlew :samples:giphy:run\n./gradlew :samples:svg:run\n./gradlew :samples:contacturi:run\n```\nYou may also find precompiled APKs on the [releases page][1].\n\nDevelopment\n-----------\nFollow the steps in the [Build](#build) section to setup the project and then edit the files however you wish.\n[Android Studio][26] cleanly imports both Glide's source and tests and is the recommended way to work with Glide.\n\nTo open the project in Android Studio:\n\n1. Go to *File* menu or the *Welcome Screen*\n2. Click on *Open...*\n3. Navigate to Glide's root directory.\n4. Select `setting.gradle`\n\nFor more details, see the [Contributing docs page][27].\n\nGetting Help\n------------\nTo report a specific problem or feature request, [open a new issue on Github][5]. For questions, suggestions, or\nanything else, email [Glide's discussion group][6], or join our IRC channel: [irc.freenode.net#glide-library][13].\n\nContributing\n------------\nBefore submitting pull requests, contributors must sign Google's [individual contributor license agreement][7].\n\nThanks\n------\n* The **Android team** and **Jake Wharton** for the [disk cache implementation][8] Glide's disk cache is based on.\n* **Dave Smith** for the [GIF decoder gist][9] Glide's GIF decoder is based on.\n* **Chris Banes** for his [gradle-mvn-push][10] script.\n* **Corey Hall** for Glide's [amazing logo][11].\n* Everyone who has contributed code and reported issues!\n\nAuthor\n------\nSam Judd - @sjudd on GitHub, @samajudd on Twitter\n\nLicense\n-------\nBSD, part MIT and Apache 2.0. See the [LICENSE][16] file for details.\n\nDisclaimer\n---------\nThis is not an official Google product.\n\n[1]: https://github.com/bumptech/glide/releases\n[2]: https://github.com/bumptech/glide/wiki\n[3]: https://bumptech.github.io/glide/ref/javadocs.html\n[4]: https://www.jetbrains.com/idea/download/\n[5]: https://github.com/bumptech/glide/blob/master/CONTRIBUTING.md\n[6]: https://groups.google.com/forum/#!forum/glidelibrary\n[7]: https://developers.google.com/open-source/cla/individual\n[8]: https://github.com/JakeWharton/DiskLruCache\n[9]: https://gist.github.com/devunwired/4479231\n[10]: https://github.com/chrisbanes/gradle-mvn-push\n[11]: static/glide_logo.png\n[12]: https://github.com/bumptech/glide/wiki/Integration-Libraries\n[13]: http://webchat.freenode.net/?channels=glide-library\n[14]: https://github.com/bumptech/glide/tree/3.0\n[15]: https://github.com/bumptech/glide/tree/master\n[16]: https://github.com/bumptech/glide/blob/master/LICENSE\n[17]: http://bumptech.github.io/glide/dev/snapshots.html\n[18]: https://github.com/bumptech/glide/issues?q=is%3Aissue+CircleImageView+OR+CircularImageView+OR+RoundedImageView\n[19]: https://github.com/wasabeef/glide-transformations\n[20]: https://bumptech.github.io/glide/\n[22]: https://muyangmin.github.io/glide-docs-cn/\n[23]: http://bumptech.github.io/glide/int/okhttp3.html\n[24]: http://bumptech.github.io/glide/int/volley.html\n[25]: http://bumptech.github.io/glide/doc/download-setup.html#proguard\n[26]: https://developer.android.com/studio/index.html\n[27]: http://bumptech.github.io/glide/dev/contributing.html\n[28]: http://bumptech.github.io/glide/doc/download-setup.html\n"
  },
  {
    "path": "annotation/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "annotation/build.gradle.kts",
    "content": "plugins {\n    id(\"java\")\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_1_7\n    targetCompatibility = JavaVersion.VERSION_1_7\n}"
  },
  {
    "path": "annotation/compiler/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "annotation/compiler/build.gradle.kts",
    "content": "plugins {\n    id(\"java\")\n}\n\ndependencies {\n    implementation(libs.javapoet)\n    implementation(libs.guava)\n\n    compileOnly(libs.autoservice)\n    compileOnly(libs.findbugs.jsr305)\n\n    implementation(project(\":annotation\"))\n    annotationProcessor(libs.autoservice)\n}\n\ntasks.withType<Javadoc> {\n    isFailOnError = false\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "annotation/compiler/gradle.properties",
    "content": "POM_NAME=Glide Annotation processor\nPOM_ARTIFACT_ID=compiler\nPOM_PACKAGING=jar\nPOM_DESCRIPTION=Glide's anntation processor. Should be included in all Applications and in all libraries that use Glide's modules for configuration.\n"
  },
  {
    "path": "annotation/compiler/proguard.pro",
    "content": "-verbose\n# Use ProGuard only to get rid of unused classes\n-dontobfuscate\n-dontoptimize\n-keepattributes *\n-keep class !com.bumptech.glide.repackaged.**,com.bumptech.glide.**\n\n# Keep the entry point to this library, see META-INF\\services\\javax.annotation.processing.Processor\n-keep class com.bumptech.glide.annotation.compiler.GlideAnnotationProcessor\n\n\n# \"duplicate definition of library class\"\n-dontnote sun.applet.**\n# \"duplicate definition of library class\"\n-dontnote sun.tools.jar.**\n# Reflective accesses in com.google.common.util.concurrent.* and some others\n-dontnote com.bumptech.glide.repackaged.com.google.common.**\n# com.google.common.collect.* and some others (….common.*.*)\n-dontwarn com.google.j2objc.annotations.Weak\n# com.google.common.util.concurrent.FuturesGetChecked$GetCheckedTypeValidatorHolder$ClassValueValidator\n-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement\n#-dontwarn **\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/AppModuleGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.Excludes;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.ParameterizedTypeName;\nimport com.squareup.javapoet.TypeName;\nimport com.squareup.javapoet.TypeSpec;\nimport com.squareup.javapoet.TypeSpec.Builder;\nimport com.squareup.javapoet.WildcardTypeName;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.ElementKind;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.element.VariableElement;\nimport javax.lang.model.type.TypeMirror;\n\n/**\n * Generates a new implementation of a AppGlideModule that calls all included LibraryGlideModules\n * and the original AppGlideModule.\n *\n * <p>The generated class will always call the AppGlideModule last to give it priority over choices\n * made or classes registered in LibraryGlideModules.\n *\n * <p>Android logging is included to allow developers to see exactly which modules are included at\n * runtime.\n *\n * <p>The generated class looks something like this:\n *\n * <pre>\n * <code>\n *  final class GeneratedAppGlideModuleImpl extends com.bumptech.glide.GeneratedAppGlideModule {\n *    private final com.bumptech.glide.samples.giphy.GiphyGlideModule appGlideModule;\n *\n *    GeneratedAppGlideModule() {\n *      appGlideModule = new com.bumptech.glide.samples.giphy.GiphyGlideModule();\n *      if (android.util.Log.isLoggable(\"Glide\", android.util.Log.DEBUG)) {\n *        android.util.Log.d(\"Glide\", \"Discovered AppGlideModule from annotation:\"\n *            + \" com.bumptech.glide.samples.giphy.GiphyGlideModule\");\n *        android.util.Log.d(\"Glide\", \"Discovered LibraryGlideModule from annotation:\"\n *            + \"com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule\");\n *      }\n *    }\n *\n *    {@literal @java.lang.Override}\n *    public void applyOptions(android.content.Context context,\n *        com.bumptech.glide.GlideBuilder builder) {\n *      appGlideModule.applyOptions(context, builder);\n *    }\n *\n *    {@literal @java.lang.Override}\n *    public void registerComponents(android.content.Context context,\n *        com.bumptech.glide.Registry registry) {\n *      new com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule()\n *          .registerComponents(context, registry);\n *      appGlideModule.registerComponents(context, registry);\n *    }\n *\n *    {@literal @java.lang.Override}\n *    public boolean isManifestParsingEnabled() {\n *      return appGlideModule.isManifestParsingEnabled();\n *    }\n *\n *    {@literal @java.lang.Override}\n *    {@literal @androidx.annotation.NonNull}\n *    public java.util.Set&lt;java.lang.Class&lt;?>> getExcludedModuleClasses() {\n *      return appGlideModule.getExcludedModuleClasses();\n *    }\n *  }\n * </code>\n * </pre>\n */\nfinal class AppModuleGenerator {\n  static final String GENERATED_ROOT_MODULE_PACKAGE_NAME = \"com.bumptech.glide\";\n  private static final String GLIDE_LOG_TAG = \"Glide\";\n  private static final String GENERATED_APP_MODULE_IMPL_SIMPLE_NAME = \"GeneratedAppGlideModuleImpl\";\n  private static final String GENERATED_ROOT_MODULE_SIMPLE_NAME = \"GeneratedAppGlideModule\";\n\n  private final ProcessingEnvironment processingEnv;\n  private final ProcessorUtil processorUtil;\n\n  AppModuleGenerator(ProcessingEnvironment processingEnv, ProcessorUtil processorUtil) {\n    this.processingEnv = processingEnv;\n    this.processorUtil = processorUtil;\n  }\n\n  TypeSpec generate(TypeElement appGlideModule, Set<String> libraryGlideModuleClassNames) {\n    ClassName appGlideModuleClassName = ClassName.get(appGlideModule);\n    List<String> excludedGlideModuleClassNames = getExcludedGlideModuleClassNames(appGlideModule);\n\n    List<String> orderedLibraryGlideModuleClassNames =\n        new ArrayList<>(libraryGlideModuleClassNames);\n    Collections.sort(orderedLibraryGlideModuleClassNames);\n\n    MethodSpec constructor =\n        generateConstructor(\n            appGlideModuleClassName,\n            orderedLibraryGlideModuleClassNames,\n            excludedGlideModuleClassNames);\n\n    MethodSpec registerComponents =\n        generateRegisterComponents(\n            orderedLibraryGlideModuleClassNames, excludedGlideModuleClassNames);\n\n    MethodSpec getExcludedModuleClasses =\n        generateGetExcludedModuleClasses(excludedGlideModuleClassNames);\n\n    MethodSpec applyOptions =\n        MethodSpec.methodBuilder(\"applyOptions\")\n            .addModifiers(Modifier.PUBLIC)\n            .addAnnotation(Override.class)\n            .addParameter(\n                ParameterSpec.builder(ClassName.get(\"android.content\", \"Context\"), \"context\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addParameter(\n                ParameterSpec.builder(\n                        ClassName.get(\"com.bumptech.glide\", \"GlideBuilder\"), \"builder\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addStatement(\"appGlideModule.applyOptions(context, builder)\", appGlideModule)\n            .build();\n\n    MethodSpec isManifestParsingEnabled =\n        MethodSpec.methodBuilder(\"isManifestParsingEnabled\")\n            .addModifiers(Modifier.PUBLIC)\n            .addAnnotation(Override.class)\n            .returns(boolean.class)\n            .addStatement(\"return appGlideModule.isManifestParsingEnabled()\", appGlideModule)\n            .build();\n\n    Builder builder =\n        TypeSpec.classBuilder(GENERATED_APP_MODULE_IMPL_SIMPLE_NAME)\n            .addModifiers(Modifier.FINAL)\n            .addAnnotation(\n                AnnotationSpec.builder(SuppressWarnings.class)\n                    .addMember(\"value\", \"$S\", \"deprecation\")\n                    .build())\n            .superclass(\n                ClassName.get(\n                    GENERATED_ROOT_MODULE_PACKAGE_NAME, GENERATED_ROOT_MODULE_SIMPLE_NAME))\n            .addField(appGlideModuleClassName, \"appGlideModule\", Modifier.PRIVATE, Modifier.FINAL)\n            .addMethod(constructor)\n            .addMethod(applyOptions)\n            .addMethod(registerComponents)\n            .addMethod(isManifestParsingEnabled)\n            .addMethod(getExcludedModuleClasses);\n\n    ClassName generatedRequestManagerFactoryClassName =\n        ClassName.get(\n            RequestManagerFactoryGenerator.GENERATED_REQUEST_MANAGER_FACTORY_PACKAGE_NAME,\n            RequestManagerFactoryGenerator.GENERATED_REQUEST_MANAGER_FACTORY_SIMPLE_NAME);\n\n    builder.addMethod(\n        MethodSpec.methodBuilder(\"getRequestManagerFactory\")\n            .addAnnotation(Override.class)\n            .addAnnotation(processorUtil.nonNull())\n            .returns(generatedRequestManagerFactoryClassName)\n            .addStatement(\"return new $T()\", generatedRequestManagerFactoryClassName)\n            .build());\n    return builder.build();\n  }\n\n  // TODO: When we drop support for parsing GlideModules from AndroidManifests, remove this method.\n  private MethodSpec generateGetExcludedModuleClasses(Collection<String> excludedClassNames) {\n    TypeName wildCardOfObject = WildcardTypeName.subtypeOf(Object.class);\n    ParameterizedTypeName classOfWildcardOfObjet =\n        ParameterizedTypeName.get(ClassName.get(Class.class), wildCardOfObject);\n    ParameterizedTypeName setOfClassOfWildcardOfObject =\n        ParameterizedTypeName.get(ClassName.get(Set.class), classOfWildcardOfObjet);\n    ParameterizedTypeName hashSetOfClassOfWildcardOfObject =\n        ParameterizedTypeName.get(ClassName.get(HashSet.class), classOfWildcardOfObjet);\n    MethodSpec.Builder builder =\n        MethodSpec.methodBuilder(\"getExcludedModuleClasses\")\n            .addModifiers(Modifier.PUBLIC)\n            .addAnnotation(Override.class)\n            .addAnnotation(processorUtil.nonNull())\n            .returns(setOfClassOfWildcardOfObject);\n\n    if (excludedClassNames.isEmpty()) {\n      builder.addStatement(\"return $T.emptySet()\", Collections.class);\n    } else {\n      builder.addStatement(\n          \"$T excludedClasses = new $T()\",\n          setOfClassOfWildcardOfObject,\n          hashSetOfClassOfWildcardOfObject);\n      for (String excludedClassName : excludedClassNames) {\n        // TODO: Remove this when we no longer support manifest parsing.\n        // Using a Literal ($L) instead of a type ($T) to get a fully qualified import that allows\n        // us to suppress deprecation warnings. Aimed at deprecated GlideModules.\n        builder.addStatement(\"excludedClasses.add($L.class)\", excludedClassName);\n      }\n      builder.addStatement(\"return excludedClasses\");\n    }\n\n    return builder.build();\n  }\n\n  private MethodSpec generateRegisterComponents(\n      Collection<String> libraryGlideModuleClassNames,\n      Collection<String> excludedGlideModuleClassNames) {\n    MethodSpec.Builder registerComponents =\n        MethodSpec.methodBuilder(\"registerComponents\")\n            .addModifiers(Modifier.PUBLIC)\n            .addAnnotation(Override.class)\n            .addParameter(\n                ParameterSpec.builder(ClassName.get(\"android.content\", \"Context\"), \"context\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addParameter(\n                ParameterSpec.builder(ClassName.get(\"com.bumptech.glide\", \"Glide\"), \"glide\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addParameter(\n                ParameterSpec.builder(ClassName.get(\"com.bumptech.glide\", \"Registry\"), \"registry\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build());\n\n    for (String glideModule : libraryGlideModuleClassNames) {\n      if (excludedGlideModuleClassNames.contains(glideModule)) {\n        continue;\n      }\n      ClassName moduleClassName = ClassName.bestGuess(glideModule);\n      registerComponents.addStatement(\n          \"new $T().registerComponents(context, glide, registry)\", moduleClassName);\n    }\n    // Order matters here. The AppGlideModule must be called last.\n    registerComponents.addStatement(\"appGlideModule.registerComponents(context, glide, registry)\");\n    return registerComponents.build();\n  }\n\n  private boolean doesAppGlideModuleConstructorAcceptContext(ClassName appGlideModule) {\n    TypeElement appGlideModuleType =\n        processingEnv.getElementUtils().getTypeElement(appGlideModule.reflectionName());\n\n    for (Element enclosed : appGlideModuleType.getEnclosedElements()) {\n      if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {\n        ExecutableElement constructor = (ExecutableElement) enclosed;\n        List<? extends VariableElement> parameters = constructor.getParameters();\n        if (parameters.isEmpty()) {\n          return false;\n        } else if (parameters.size() > 1) {\n          throw new IllegalStateException(\n              \"Constructor for \"\n                  + appGlideModule\n                  + \" accepts too many parameters\"\n                  + \", it should accept no parameters, or a single Context\");\n        } else {\n          VariableElement parameter = parameters.get(0);\n          TypeMirror parameterType = parameter.asType();\n          TypeMirror contextType =\n              processingEnv.getElementUtils().getTypeElement(\"android.content.Context\").asType();\n          if (!processingEnv.getTypeUtils().isSameType(parameterType, contextType)) {\n            throw new IllegalStateException(\"Unrecognized type: \" + parameterType);\n          }\n          return true;\n        }\n      }\n    }\n    return false;\n  }\n\n  private MethodSpec generateConstructor(\n      ClassName appGlideModule,\n      Collection<String> libraryGlideModuleClassNames,\n      Collection<String> excludedGlideModuleClassNames) {\n    MethodSpec.Builder constructorBuilder =\n        MethodSpec.constructorBuilder()\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(\n                ParameterSpec.builder(ClassName.get(\"android.content\", \"Context\"), \"context\")\n                    .build());\n\n    if (doesAppGlideModuleConstructorAcceptContext(appGlideModule)) {\n      constructorBuilder.addStatement(\"appGlideModule = new $T(context)\", appGlideModule);\n    } else {\n      constructorBuilder.addStatement(\"appGlideModule = new $T()\", appGlideModule);\n    }\n\n    ClassName androidLogName = ClassName.get(\"android.util\", \"Log\");\n\n    // Add some log lines to indicate to developers which modules where discovered.\n    constructorBuilder.beginControlFlow(\n        \"if ($T.isLoggable($S, $T.DEBUG))\", androidLogName, GLIDE_LOG_TAG, androidLogName);\n    constructorBuilder.addStatement(\n        \"$T.d($S, $S)\",\n        androidLogName,\n        GLIDE_LOG_TAG,\n        \"Discovered AppGlideModule from annotation: \" + appGlideModule);\n    // Excluded GlideModule classes from the manifest are logged in Glide's singleton.\n    for (String glideModule : libraryGlideModuleClassNames) {\n      if (excludedGlideModuleClassNames.contains(glideModule)) {\n        constructorBuilder.addStatement(\n            \"$T.d($S, $S)\",\n            androidLogName,\n            GLIDE_LOG_TAG,\n            \"AppGlideModule excludes LibraryGlideModule from annotation: \" + glideModule);\n      } else {\n        constructorBuilder.addStatement(\n            \"$T.d($S, $S)\",\n            androidLogName,\n            GLIDE_LOG_TAG,\n            \"Discovered LibraryGlideModule from annotation: \" + glideModule);\n      }\n    }\n    constructorBuilder.endControlFlow();\n    return constructorBuilder.build();\n  }\n\n  private List<String> getExcludedGlideModuleClassNames(TypeElement appGlideModule) {\n    Set<String> names =\n        processorUtil.findClassValuesFromAnnotationOnClassAsNames(appGlideModule, Excludes.class);\n    List<String> result = new ArrayList<>(names);\n    Collections.sort(result);\n    return result;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/AppModuleProcessor.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.squareup.javapoet.TypeSpec;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.PackageElement;\nimport javax.lang.model.element.TypeElement;\n\n/**\n * Runs the final steps of Glide's annotation process and generates the combined {@code\n * AppGlideModule}, {@code com.bumptech.glide.Glide}, {@code com.bumptech.glide.RequestManager}, and\n * {@code com.bumptech.glide.request.RequestOptions} classes.\n */\nfinal class AppModuleProcessor {\n  private static final String COMPILER_PACKAGE_NAME =\n      GlideAnnotationProcessor.class.getPackage().getName();\n\n  private final ProcessingEnvironment processingEnv;\n  private final ProcessorUtil processorUtil;\n  private final List<TypeElement> appGlideModules = new ArrayList<>();\n  private final RequestOptionsGenerator requestOptionsGenerator;\n  private final RequestManagerGenerator requestManagerGenerator;\n  private final AppModuleGenerator appModuleGenerator;\n  private final RequestBuilderGenerator requestBuilderGenerator;\n  private final RequestManagerFactoryGenerator requestManagerFactoryGenerator;\n  private final GlideGenerator glideGenerator;\n\n  AppModuleProcessor(ProcessingEnvironment processingEnv, ProcessorUtil processorUtil) {\n    this.processingEnv = processingEnv;\n    this.processorUtil = processorUtil;\n\n    appModuleGenerator = new AppModuleGenerator(processingEnv, processorUtil);\n    requestOptionsGenerator = new RequestOptionsGenerator(processingEnv, processorUtil);\n    requestManagerGenerator = new RequestManagerGenerator(processingEnv, processorUtil);\n    requestManagerFactoryGenerator =\n        new RequestManagerFactoryGenerator(processingEnv, processorUtil);\n    glideGenerator = new GlideGenerator(processingEnv, processorUtil);\n    requestBuilderGenerator = new RequestBuilderGenerator(processingEnv, processorUtil);\n  }\n\n  void processModules(Set<? extends TypeElement> set, RoundEnvironment env) {\n    for (TypeElement element : processorUtil.getElementsFor(GlideModule.class, env)) {\n      if (processorUtil.isAppGlideModule(element)) {\n        appGlideModules.add(element);\n      }\n    }\n\n    processorUtil.debugLog(\"got app modules: \" + appGlideModules);\n\n    if (appGlideModules.size() > 1) {\n      throw new IllegalStateException(\n          \"You cannot have more than one AppGlideModule, found: \" + appGlideModules);\n    }\n  }\n\n  boolean maybeWriteAppModule() {\n    // appGlideModules is added to in order to catch errors where multiple AppGlideModules may be\n    // present for a single application or library. Because we only add to appGlideModules, we use\n    // isGeneratedAppGlideModuleWritten to make sure the GeneratedAppGlideModule is written at\n    // most once.\n    if (appGlideModules.isEmpty()) {\n      return false;\n    }\n    TypeElement appModule = appGlideModules.get(0);\n    processorUtil.debugLog(\"Processing app module: \" + appModule);\n    // If this package is null, it means there are no classes with this package name. One way this\n    // could happen is if we process an annotation and reach this point without writing something\n    // to the package. We do not error check here because that shouldn't happen with the\n    // current implementation.\n    PackageElement glideGenPackage =\n        processingEnv.getElementUtils().getPackageElement(COMPILER_PACKAGE_NAME);\n    FoundIndexedClassNames indexedClassNames = getIndexedClassNames(glideGenPackage);\n\n    // Write all generated code to the package containing the AppGlideModule. Doing so fixes\n    // classpath collisions if more than one Application containing a AppGlideModule is included\n    // in a project.\n    String generatedCodePackageName = appModule.getEnclosingElement().toString();\n\n    TypeSpec generatedRequestOptions =\n        requestOptionsGenerator.generate(generatedCodePackageName, indexedClassNames.extensions);\n    writeRequestOptions(generatedCodePackageName, generatedRequestOptions);\n\n    TypeSpec generatedRequestBuilder =\n        requestBuilderGenerator.generate(\n            generatedCodePackageName, indexedClassNames.extensions, generatedRequestOptions);\n    writeRequestBuilder(generatedCodePackageName, generatedRequestBuilder);\n\n    TypeSpec requestManager =\n        requestManagerGenerator.generate(\n            generatedCodePackageName,\n            generatedRequestOptions,\n            generatedRequestBuilder,\n            indexedClassNames.extensions);\n    writeRequestManager(generatedCodePackageName, requestManager);\n\n    TypeSpec requestManagerFactory =\n        requestManagerFactoryGenerator.generate(generatedCodePackageName, requestManager);\n    writeRequestManagerFactory(requestManagerFactory);\n\n    TypeSpec glide =\n        glideGenerator.generate(generatedCodePackageName, getGlideName(appModule), requestManager);\n    writeGlide(generatedCodePackageName, glide);\n\n    TypeSpec generatedAppGlideModule =\n        appModuleGenerator.generate(appModule, indexedClassNames.glideModules);\n    writeAppModule(generatedAppGlideModule);\n\n    processorUtil.infoLog(\"Wrote GeneratedAppGlideModule with: \" + indexedClassNames.glideModules);\n\n    return true;\n  }\n\n  private String getGlideName(TypeElement appModule) {\n    return appModule.getAnnotation(GlideModule.class).glideName();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private FoundIndexedClassNames getIndexedClassNames(PackageElement glideGenPackage) {\n    Set<String> glideModules = new HashSet<>();\n    Set<String> extensions = new HashSet<>();\n    List<? extends Element> glideGeneratedElements = glideGenPackage.getEnclosedElements();\n    for (Element indexer : glideGeneratedElements) {\n      Index annotation = indexer.getAnnotation(Index.class);\n      // If the annotation is null, it means we've come across another class in the same package\n      // that we can safely ignore.\n      if (annotation != null) {\n        Collections.addAll(glideModules, annotation.modules());\n        Collections.addAll(extensions, annotation.extensions());\n      }\n    }\n\n    processorUtil.debugLog(\"Found GlideModules: \" + glideModules);\n    return new FoundIndexedClassNames(glideModules, extensions);\n  }\n\n  private void writeGlide(String packageName, TypeSpec glide) {\n    processorUtil.writeClass(packageName, glide);\n  }\n\n  private void writeRequestManager(String packageName, TypeSpec requestManager) {\n    processorUtil.writeClass(packageName, requestManager);\n  }\n\n  // We dont' care about collisions in IDEs since this class isn't an API class.\n  private void writeRequestManagerFactory(TypeSpec requestManagerFactory) {\n    processorUtil.writeClass(\n        AppModuleGenerator.GENERATED_ROOT_MODULE_PACKAGE_NAME, requestManagerFactory);\n  }\n\n  // The app module we generate subclasses a package private class. We don't care about classpath\n  // collisions in IDEs since this class isn't an API class.\n  private void writeAppModule(TypeSpec appModule) {\n    processorUtil.writeClass(AppModuleGenerator.GENERATED_ROOT_MODULE_PACKAGE_NAME, appModule);\n  }\n\n  private void writeRequestOptions(String packageName, TypeSpec requestOptions) {\n    processorUtil.writeClass(packageName, requestOptions);\n  }\n\n  private void writeRequestBuilder(String packageName, TypeSpec requestBuilder) {\n    processorUtil.writeClass(packageName, requestBuilder);\n  }\n\n  private static final class FoundIndexedClassNames {\n    private final Set<String> glideModules;\n    private final Set<String> extensions;\n\n    private FoundIndexedClassNames(Set<String> glideModules, Set<String> extensions) {\n      this.glideModules = glideModules;\n      this.extensions = extensions;\n    }\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/ExtensionProcessor.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.squareup.javapoet.TypeSpec;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.lang.model.element.TypeElement;\n\n/**\n * Writes Indexer classes annotated with {@link Index} for all classes found annotated with {@link\n * GlideExtension}.\n */\nfinal class ExtensionProcessor {\n  private final ProcessorUtil processorUtil;\n  private final IndexerGenerator indexerGenerator;\n  private final GlideExtensionValidator extensionValidator;\n\n  ExtensionProcessor(\n      ProcessingEnvironment processingEnvironment,\n      ProcessorUtil processorUtil,\n      IndexerGenerator indexerGenerator) {\n    this.processorUtil = processorUtil;\n    this.indexerGenerator = indexerGenerator;\n    extensionValidator = new GlideExtensionValidator(processingEnvironment, processorUtil);\n  }\n\n  boolean processExtensions(RoundEnvironment env) {\n    List<TypeElement> elements = processorUtil.getElementsFor(GlideExtension.class, env);\n    processorUtil.debugLog(\"Processing types : \" + elements);\n    for (TypeElement typeElement : elements) {\n      extensionValidator.validateExtension(typeElement);\n      processorUtil.debugLog(\"Processing elements: \" + typeElement.getEnclosedElements());\n    }\n\n    if (elements.isEmpty()) {\n      return false;\n    }\n    TypeSpec spec = indexerGenerator.generate(elements);\n    processorUtil.writeIndexer(spec);\n    return true;\n  }\n\n  Set<String> getSupportedAnnotationTypes() {\n    return Collections.singleton(GlideExtension.class.getName());\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/GlideAnnotationProcessor.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideType;\nimport com.google.auto.service.AutoService;\nimport java.util.HashSet;\nimport java.util.Set;\nimport javax.annotation.processing.AbstractProcessor;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.annotation.processing.Processor;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.lang.model.SourceVersion;\nimport javax.lang.model.element.TypeElement;\n\n// Links in Javadoc will work due to build setup, even though there is no direct dependency here.\n/**\n * Generates classes based on Glide's annotations that configure Glide, add support for additional\n * resource types, and/or extend Glide's API.\n *\n * <p>This processor discovers all {@code AppGlideModule} and {@code LibraryGlideModule}\n * implementations that are annotated with {@link com.bumptech.glide.annotation.GlideModule}. Any\n * implementations missing the annotation will be ignored.\n *\n * <p>This processor also discovers all {@link com.bumptech.glide.annotation.GlideExtension}\n * annotated classes.\n *\n * <p>Multiple classes are generated by this processor:\n *\n * <ul>\n *   <li>For {@code LibraryGlideModule}s - A GlideIndexer class in a specific package that will\n *       later be used by the processor to discover all {@code LibraryGlideModule} classes.\n *   <li>For {@code AppGlideModule}s - A single {@code AppGlideModule} implementation ({@code\n *       com.bumptech.glide.GeneratedAppGlideModule}) that calls all {@code LibraryGlideModule}s and\n *       the original {@code AppGlideModule} in the correct order when Glide is initialized.\n *   <li>{@link com.bumptech.glide.annotation.GlideExtension}s -\n *       <ul>\n *         <li>A {@code com.bumptech.glide.request.RequestOptions} implementation that contains\n *             static versions of all builder methods in the base class and both static and instance\n *             versions of methods in all {@link com.bumptech.glide.annotation.GlideExtension}s.\n *         <li>If one or more methods in one or more {@link\n *             com.bumptech.glide.annotation.GlideExtension} annotated classes are annotated with\n *             {@link GlideType}:\n *             <ul>\n *               <li>A {@code com.bumptech.glide.RequestManager} implementation containing a\n *                   generated method for each method annotated with {@link GlideType}.\n *               <li>A {@code\n *                   com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory}\n *                   implementation that produces the generated {@code\n *                   com.bumptech.glide.RequestManager}s.\n *               <li>A {@code com.bumptech.glide.Glide} look-alike that implements all static\n *                   methods in the {@code com.bumptech.glide.Glide} singleton and returns the\n *                   generated {@code com.bumptech.glide.RequestManager} implementation when\n *                   appropriate.\n *             </ul>\n *       </ul>\n * </ul>\n *\n * <p>{@code AppGlideModule} implementations must only be included in applications, not in\n * libraries. There must be exactly one {@code AppGlideModule} implementation per Application. The\n * {@code AppGlideModule} class is used as a signal that all modules have been found and that the\n * final merged {@code com.bumptech.glide.GeneratedAppGlideModule} impl can be created.\n */\n@AutoService(Processor.class)\npublic final class GlideAnnotationProcessor extends AbstractProcessor {\n  static final boolean DEBUG = false;\n  private ProcessorUtil processorUtil;\n  private LibraryModuleProcessor libraryModuleProcessor;\n  private AppModuleProcessor appModuleProcessor;\n  private boolean isGeneratedAppGlideModuleWritten;\n  private ExtensionProcessor extensionProcessor;\n\n  @Override\n  public synchronized void init(ProcessingEnvironment processingEnvironment) {\n    super.init(processingEnvironment);\n    processorUtil = new ProcessorUtil(processingEnvironment);\n    IndexerGenerator indexerGenerator = new IndexerGenerator(processorUtil);\n    libraryModuleProcessor = new LibraryModuleProcessor(processorUtil, indexerGenerator);\n    appModuleProcessor = new AppModuleProcessor(processingEnvironment, processorUtil);\n    extensionProcessor =\n        new ExtensionProcessor(processingEnvironment, processorUtil, indexerGenerator);\n  }\n\n  @Override\n  public Set<String> getSupportedAnnotationTypes() {\n    Set<String> result = new HashSet<>();\n    result.addAll(libraryModuleProcessor.getSupportedAnnotationTypes());\n    result.addAll(extensionProcessor.getSupportedAnnotationTypes());\n    return result;\n  }\n\n  @Override\n  public SourceVersion getSupportedSourceVersion() {\n    return SourceVersion.latestSupported();\n  }\n\n  /**\n   * Each round we do the following:\n   *\n   * <ol>\n   *   <li>Find all {@code AppGlideModule}s and save them to an instance variable (throw if > 1).\n   *   <li>Find all {@code LibraryGlideModule}s\n   *   <li>For each {@code LibraryGlideModule}, write an {@code Indexer} with an Annotation with the\n   *       class name.\n   *   <li>If we wrote any {@code Indexer}s, return and wait for the next round.\n   *   <li>If we didn't write any {@code Indexer}s and there is a {@code AppGlideModule}, write the\n   *       {@code GeneratedAppGlideModule}. Once the {@code GeneratedAppGlideModule} is written, we\n   *       expect to be finished. Any further generation of related classes will result in errors.\n   * </ol>\n   */\n  @Override\n  public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {\n    processorUtil.process();\n    boolean newModulesWritten = libraryModuleProcessor.processModules(env);\n    boolean newExtensionWritten = extensionProcessor.processExtensions(env);\n    appModuleProcessor.processModules(set, env);\n\n    if (newExtensionWritten || newModulesWritten) {\n      if (isGeneratedAppGlideModuleWritten) {\n        throw new IllegalStateException(\"Cannot process annotations after writing AppGlideModule\");\n      }\n      return false;\n    }\n\n    if (!isGeneratedAppGlideModuleWritten) {\n      isGeneratedAppGlideModuleWritten = appModuleProcessor.maybeWriteAppModule();\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/GlideExtensionValidator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.ProcessorUtil.nonNulls;\n\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.annotation.GlideType;\nimport com.google.common.base.Function;\nimport com.google.common.collect.FluentIterable;\nimport com.squareup.javapoet.ClassName;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.AnnotationMirror;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.ElementKind;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.element.VariableElement;\nimport javax.lang.model.type.DeclaredType;\nimport javax.lang.model.type.TypeMirror;\nimport javax.tools.Diagnostic.Kind;\n\n/**\n * Validates that classes annotated with {@link com.bumptech.glide.annotation.GlideExtension}\n * contains methods with the expected format.\n *\n * <p>Validation is performed so that errors can be found when a library is compiled. Without\n * validation, an error written in to a library wouldn't be found until Glide tried to generate code\n * for an Application.\n */\nfinal class GlideExtensionValidator {\n  private final ProcessingEnvironment processingEnvironment;\n  private final ProcessorUtil processorUtil;\n\n  GlideExtensionValidator(\n      ProcessingEnvironment processingEnvironment, ProcessorUtil processorUtil) {\n    this.processingEnvironment = processingEnvironment;\n    this.processorUtil = processorUtil;\n  }\n\n  void validateExtension(TypeElement typeElement) {\n    if (!typeElement.getModifiers().contains(Modifier.PUBLIC)) {\n      throw new IllegalArgumentException(\n          \"RequestOptionsExtensions must be public, including: \" + getName(typeElement));\n    }\n    for (Element element : typeElement.getEnclosedElements()) {\n      if (element.getKind() == ElementKind.CONSTRUCTOR) {\n        validateExtensionConstructor(element);\n      } else if (element.getKind() == ElementKind.METHOD) {\n        ExecutableElement executableElement = (ExecutableElement) element;\n        if (executableElement.getAnnotation(GlideOption.class) != null) {\n          validateGlideOption(executableElement);\n        } else if (executableElement.getAnnotation(GlideType.class) != null) {\n          validateGlideType(executableElement);\n        }\n      }\n    }\n  }\n\n  private static String getQualifiedMethodName(ExecutableElement executableElement) {\n    return getEnclosingClassName(executableElement) + \"#\" + getName(executableElement);\n  }\n\n  private static String getEnclosingClassName(Element element) {\n    return element.getEnclosingElement().toString();\n  }\n\n  private static String getName(Element element) {\n    return element.toString();\n  }\n\n  private static void validateExtensionConstructor(Element element) {\n    if (!element.getModifiers().contains(Modifier.PRIVATE)) {\n      throw new IllegalArgumentException(\n          \"RequestOptionsExtensions must be public, with private constructors and only static\"\n              + \" methods. Found a non-private constructor in: \"\n              + getEnclosingClassName(element));\n    }\n    ExecutableElement executableElement = (ExecutableElement) element;\n    if (!executableElement.getParameters().isEmpty()) {\n      throw new IllegalArgumentException(\n          \"RequestOptionsExtensions must be public, with private constructors and only static\"\n              + \" methods. Found parameters in the constructor of: \"\n              + getEnclosingClassName(element));\n    }\n  }\n\n  private void validateGlideOption(ExecutableElement executableElement) {\n    validateGlideOptionAnnotations(executableElement);\n    validateGlideOptionParameters(executableElement);\n    TypeMirror returnType = executableElement.getReturnType();\n    if (!isBaseRequestOptions(returnType)) {\n      throw new IllegalArgumentException(\n          \"@GlideOption methods should return a\"\n              + \" BaseRequestOptions<?> object, but \"\n              + getQualifiedMethodName(executableElement)\n              + \" returns \"\n              + returnType\n              + \". If you're using old style @GlideOption methods, your\"\n              + \" method may have a void return type, but doing so is deprecated and support will\"\n              + \" be removed in a future version\");\n    }\n    validateGlideOptionOverride(executableElement);\n  }\n\n  private void validateGlideOptionAnnotations(ExecutableElement executableElement) {\n    validateAnnotatedNonNull(executableElement);\n  }\n\n  private static void validateGlideOptionParameters(ExecutableElement executableElement) {\n    if (executableElement.getParameters().isEmpty()) {\n      throw new IllegalArgumentException(\n          \"@GlideOption methods must take a \"\n              + \"BaseRequestOptions<?> object as their first parameter, but \"\n              + getQualifiedMethodName(executableElement)\n              + \" has none\");\n    }\n    VariableElement first = executableElement.getParameters().get(0);\n    TypeMirror expected = first.asType();\n    if (!isBaseRequestOptions(expected)) {\n      throw new IllegalArgumentException(\n          \"@GlideOption methods must take a\"\n              + \" BaseRequestOptions<?> object as their first parameter, but the first parameter\"\n              + \" in \"\n              + getQualifiedMethodName(executableElement)\n              + \" is \"\n              + expected);\n    }\n  }\n\n  private static boolean isBaseRequestOptions(TypeMirror typeMirror) {\n    return typeMirror.toString().equals(\"com.bumptech.glide.request.BaseRequestOptions<?>\");\n  }\n\n  private void validateGlideOptionOverride(ExecutableElement element) {\n    int overrideType = processorUtil.getOverrideType(element);\n    boolean isOverridingBaseRequestOptionsMethod = isMethodInBaseRequestOptions(element);\n    if (isOverridingBaseRequestOptionsMethod && overrideType == GlideOption.OVERRIDE_NONE) {\n      throw new IllegalArgumentException(\n          \"Accidentally attempting to override a method in\"\n              + \" BaseRequestOptions. Add an 'override' value in the @GlideOption annotation\"\n              + \" if this is intentional. Offending method: \"\n              + getQualifiedMethodName(element));\n    } else if (!isOverridingBaseRequestOptionsMethod && overrideType != GlideOption.OVERRIDE_NONE) {\n      throw new IllegalArgumentException(\n          \"Requested to override an existing method in\"\n              + \" BaseRequestOptions, but no such method was found. Offending method: \"\n              + getQualifiedMethodName(element));\n    }\n  }\n\n  private boolean isMethodInBaseRequestOptions(ExecutableElement toFind) {\n    // toFind is a method in a GlideExtension whose first argument is a BaseRequestOptions<?> type.\n    // Since we're comparing against methods in BaseRequestOptions itself, we need to drop that\n    // first type.\n    TypeElement requestOptionsType =\n        processingEnvironment\n            .getElementUtils()\n            .getTypeElement(RequestOptionsGenerator.BASE_REQUEST_OPTIONS_QUALIFIED_NAME);\n    List<String> toFindParameterNames = getComparableParameterNames(toFind, true /*skipFirst*/);\n    String toFindSimpleName = toFind.getSimpleName().toString();\n    for (Element element : requestOptionsType.getEnclosedElements()) {\n      if (element.getKind() != ElementKind.METHOD) {\n        continue;\n      }\n      ExecutableElement inBase = (ExecutableElement) element;\n      if (toFindSimpleName.equals(inBase.getSimpleName().toString())) {\n        List<String> parameterNamesInBase =\n            getComparableParameterNames(inBase, false /*skipFirst*/);\n        if (parameterNamesInBase.equals(toFindParameterNames)) {\n          return true;\n        }\n      }\n    }\n    return false;\n  }\n\n  private static List<String> getComparableParameterNames(\n      ExecutableElement element, boolean skipFirst) {\n    List<? extends VariableElement> parameters = element.getParameters();\n    if (skipFirst) {\n      parameters = parameters.subList(1, parameters.size());\n    }\n    List<String> result = new ArrayList<>(parameters.size());\n    for (VariableElement parameter : parameters) {\n      result.add(parameter.asType().toString());\n    }\n    return result;\n  }\n\n  private void validateGlideType(ExecutableElement executableElement) {\n    TypeMirror returnType = executableElement.getReturnType();\n    validateGlideTypeAnnotations(executableElement);\n    if (!isRequestBuilder(returnType) || !typeMatchesExpected(returnType, executableElement)) {\n      String expectedClassName = getGlideTypeValue(executableElement);\n      throw new IllegalArgumentException(\n          \"@GlideType methods should return a RequestBuilder<\"\n              + expectedClassName\n              + \"> object, but \"\n              + getQualifiedMethodName(executableElement)\n              + \" returns: \"\n              + returnType\n              + \". If you're using old style @GlideType methods, your\"\n              + \" method may have a void return type, but doing so is deprecated and support will\"\n              + \" be removed in a future version\");\n    }\n    validateGlideTypeParameters(executableElement);\n  }\n\n  private String getGlideTypeValue(ExecutableElement executableElement) {\n    return processorUtil\n        .findClassValuesFromAnnotationOnClassAsNames(executableElement, GlideType.class)\n        .iterator()\n        .next();\n  }\n\n  private boolean typeMatchesExpected(TypeMirror returnType, ExecutableElement executableElement) {\n    if (!(returnType instanceof DeclaredType)) {\n      return false;\n    }\n    List<? extends TypeMirror> typeArguments = ((DeclaredType) returnType).getTypeArguments();\n    if (typeArguments.size() != 1) {\n      return false;\n    }\n    TypeMirror argument = typeArguments.get(0);\n    String expected = getGlideTypeValue(executableElement);\n    return argument.toString().equals(expected);\n  }\n\n  private boolean isRequestBuilder(TypeMirror typeMirror) {\n    TypeMirror toCompare = processingEnvironment.getTypeUtils().erasure(typeMirror);\n    return toCompare.toString().equals(\"com.bumptech.glide.RequestBuilder\");\n  }\n\n  private static void validateGlideTypeParameters(ExecutableElement executableElement) {\n    if (executableElement.getParameters().size() != 1) {\n      throw new IllegalArgumentException(\n          \"@GlideType methods must take a\"\n              + \" RequestBuilder object as their first and only parameter, but given multiple for: \"\n              + getQualifiedMethodName(executableElement));\n    }\n\n    VariableElement first = executableElement.getParameters().get(0);\n    TypeMirror argumentType = first.asType();\n    if (!argumentType.toString().startsWith(\"com.bumptech.glide.RequestBuilder\")) {\n      throw new IllegalArgumentException(\n          \"@GlideType methods must take a\"\n              + \" RequestBuilder object as their first and only parameter, but given: \"\n              + argumentType\n              + \" for: \"\n              + getQualifiedMethodName(executableElement));\n    }\n  }\n\n  private void validateGlideTypeAnnotations(ExecutableElement executableElement) {\n    validateAnnotatedNonNull(executableElement);\n  }\n\n  private void validateAnnotatedNonNull(ExecutableElement executableElement) {\n    Set<String> annotationNames =\n        FluentIterable.from(executableElement.getAnnotationMirrors())\n            .transform(\n                new Function<AnnotationMirror, String>() {\n                  @Override\n                  public String apply(AnnotationMirror input) {\n                    return input.getAnnotationType().asElement().toString();\n                  }\n                })\n            .toSet();\n    boolean noNonNull = true;\n    for (ClassName nonNull : nonNulls()) {\n      if (annotationNames.contains(nonNull.reflectionName())) {\n        noNonNull = false;\n        break;\n      }\n    }\n    if (noNonNull) {\n      processingEnvironment\n          .getMessager()\n          .printMessage(\n              Kind.WARNING,\n              getQualifiedMethodName(executableElement)\n                  + \" is missing the \"\n                  + processorUtil.nonNull().reflectionName()\n                  + \" annotation,\"\n                  + \" please add it to ensure that your extension methods are always returning\"\n                  + \" non-null values\");\n    }\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/GlideGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.google.common.base.Function;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.Lists;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.MethodSpec.Builder;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.TypeSpec;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.AnnotationMirror;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.util.Elements;\n\n/**\n * Generates a Glide look-alike that acts as the entry point to the generated API\n * (GlideApp.with(...)).\n *\n * <p>Generated {@code com.bumptech.glide.Glide} look-alikes look like this (note that the name is\n * configurable in {@link com.bumptech.glide.annotation.GlideModule}):\n *\n * <pre>\n * <code>\n * public final class GlideApp {\n *   private GiphyGlide() {\n *   }\n *\n *   public static File getPhotoCacheDir(Context context) {\n *     return Glide.getPhotoCacheDir(context);\n *   }\n *\n *   public static File getPhotoCacheDir(Context context, String cacheName) {\n *     return Glide.getPhotoCacheDir(context, cacheName);\n *   }\n *\n *   public static Glide get(Context context) {\n *     return Glide.get(context);\n *   }\n *\n *   public static void tearDown() {\n *     Glide.tearDown();\n *   }\n *\n *   public static GeneratedRequestManager with(Context context) {\n *     return (GeneratedRequestManager) Glide.with(context);\n *   }\n *\n *   public static GeneratedRequestManager with(Activity activity) {\n *    return (GeneratedRequestManager) Glide.with(activity);\n *   }\n *\n *   public static GeneratedRequestManager with(FragmentActivity activity) {\n *     return (GeneratedRequestManager) Glide.with(activity);\n *   }\n *\n *   public static GeneratedRequestManager with(Fragment fragment) {\n *     return (GeneratedRequestManager) Glide.with(fragment);\n *   }\n *\n *   public static GeneratedRequestManager with(androidx.fragment.app.Fragment fragment) {\n *     return (GeneratedRequestManager) Glide.with(fragment);\n *   }\n * </code>\n * </pre>\n */\nfinal class GlideGenerator {\n  private static final String GLIDE_QUALIFIED_NAME = \"com.bumptech.glide.Glide\";\n\n  private static final String REQUEST_MANAGER_QUALIFIED_NAME = \"com.bumptech.glide.RequestManager\";\n\n  private static final String SUPPRESS_LINT_PACKAGE_NAME = \"android.annotation\";\n  private static final String SUPPRESS_LINT_CLASS_NAME = \"SuppressLint\";\n\n  private final ProcessingEnvironment processingEnv;\n  private final ProcessorUtil processorUtil;\n  private final TypeElement glideType;\n  private final TypeElement requestManagerType;\n\n  GlideGenerator(ProcessingEnvironment processingEnv, ProcessorUtil processorUtil) {\n    this.processingEnv = processingEnv;\n    this.processorUtil = processorUtil;\n\n    Elements elementUtils = processingEnv.getElementUtils();\n\n    requestManagerType = elementUtils.getTypeElement(REQUEST_MANAGER_QUALIFIED_NAME);\n\n    glideType = elementUtils.getTypeElement(GLIDE_QUALIFIED_NAME);\n  }\n\n  TypeSpec generate(\n      String generatedCodePackageName, String glideName, TypeSpec generatedRequestManager) {\n    return TypeSpec.classBuilder(glideName)\n        .addJavadoc(\n            \"The entry point for interacting with Glide for Applications\\n\"\n                + \"\\n\"\n                + \"<p>Includes all generated APIs from all\\n\"\n                + \"{@link $T}s in source and dependent libraries.\\n\"\n                + \"\\n\"\n                + \"<p>This class is generated and should not be modified\"\n                + \"\\n\"\n                + \"@see $T\\n\",\n            GlideExtension.class,\n            glideType)\n        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n        .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())\n        .addMethods(\n            generateOverridesForGlideMethods(generatedCodePackageName, generatedRequestManager))\n        .build();\n  }\n\n  private List<MethodSpec> generateOverridesForGlideMethods(\n      final String generatedCodePackageName, final TypeSpec generatedRequestManager) {\n    return Lists.transform(\n        discoverGlideMethodsToOverride(),\n        new Function<ExecutableElement, MethodSpec>() {\n          @Override\n          public MethodSpec apply(ExecutableElement input) {\n            if (isGlideWithMethod(input)) {\n              return overrideGlideWithMethod(\n                  generatedCodePackageName, generatedRequestManager, input);\n            } else {\n              return overrideGlideStaticMethod(input);\n            }\n          }\n        });\n  }\n\n  private MethodSpec overrideGlideStaticMethod(ExecutableElement methodToOverride) {\n    List<ParameterSpec> parameters = processorUtil.getParameters(methodToOverride);\n\n    TypeElement element =\n        (TypeElement) processingEnv.getTypeUtils().asElement(methodToOverride.getReturnType());\n\n    MethodSpec.Builder builder =\n        MethodSpec.methodBuilder(methodToOverride.getSimpleName().toString())\n            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n            .addJavadoc(processorUtil.generateSeeMethodJavadoc(methodToOverride))\n            .addParameters(parameters);\n\n    addReturnAnnotations(builder, methodToOverride);\n\n    boolean returnsValue = element != null;\n    if (returnsValue) {\n      builder.returns(ClassName.get(element));\n    }\n\n    StringBuilder code = new StringBuilder(returnsValue ? \"return \" : \"\");\n    code.append(\"$T.$N(\");\n    List<Object> args = new ArrayList<>();\n    args.add(ClassName.get(glideType));\n    args.add(methodToOverride.getSimpleName());\n    if (!parameters.isEmpty()) {\n      for (ParameterSpec param : parameters) {\n        code.append(\"$L, \");\n        args.add(param.name);\n      }\n      code = new StringBuilder(code.substring(0, code.length() - 2));\n    }\n    code.append(\")\");\n    builder.addStatement(code.toString(), args.toArray(new Object[0]));\n    return builder.build();\n  }\n\n  private Builder addReturnAnnotations(Builder builder, ExecutableElement methodToOverride) {\n    Elements elements = processingEnv.getElementUtils();\n    TypeElement visibleForTestingTypeElement =\n        elements.getTypeElement(processorUtil.visibleForTesting().reflectionName());\n    String visibleForTestingTypeQualifiedName = visibleForTestingTypeElement.toString();\n\n    for (AnnotationMirror mirror : methodToOverride.getAnnotationMirrors()) {\n      builder.addAnnotation(AnnotationSpec.get(mirror));\n\n      // Suppress a lint warning if we're overriding a VisibleForTesting method.\n      // See #1977.\n      String annotationQualifiedName = mirror.getAnnotationType().toString();\n      if (annotationQualifiedName.equals(visibleForTestingTypeQualifiedName)) {\n        builder.addAnnotation(\n            AnnotationSpec.builder(\n                    ClassName.get(SUPPRESS_LINT_PACKAGE_NAME, SUPPRESS_LINT_CLASS_NAME))\n                .addMember(\"value\", \"$S\", \"VisibleForTests\")\n                .build());\n      }\n    }\n\n    return builder;\n  }\n\n  private List<ExecutableElement> discoverGlideMethodsToOverride() {\n    return processorUtil.findStaticMethods(glideType);\n  }\n\n  private boolean isGlideWithMethod(ExecutableElement element) {\n    return processorUtil.isReturnValueTypeMatching(element, requestManagerType);\n  }\n\n  private MethodSpec overrideGlideWithMethod(\n      String packageName, TypeSpec generatedRequestManager, ExecutableElement methodToOverride) {\n    ClassName generatedRequestManagerClassName =\n        ClassName.get(packageName, generatedRequestManager.name);\n    List<ParameterSpec> parameters = processorUtil.getParameters(methodToOverride);\n    Preconditions.checkArgument(\n        parameters.size() == 1, \"Expected size of 1, but got %s\", methodToOverride);\n    ParameterSpec parameter = parameters.iterator().next();\n\n    Builder builder =\n        MethodSpec.methodBuilder(methodToOverride.getSimpleName().toString())\n            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n            .addJavadoc(processorUtil.generateSeeMethodJavadoc(methodToOverride))\n            .addParameters(parameters)\n            .returns(generatedRequestManagerClassName)\n            .addStatement(\n                \"return ($T) $T.$N($L)\",\n                generatedRequestManagerClassName,\n                glideType,\n                methodToOverride.getSimpleName().toString(),\n                parameter.name);\n\n    return addReturnAnnotations(builder, methodToOverride).build();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/IndexerGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.TypeSpec;\nimport java.lang.annotation.Annotation;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.UUID;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\n\n/**\n * Generates an empty class with an annotation containing the class names of one or more\n * LibraryGlideModules and/or one or more GlideExtensions.\n *\n * <p>We use a separate class so that LibraryGlideModules and GlideExtensions written in libraries\n * can be bundled into an AAR and later retrieved by the annotation processor when it processes the\n * AppGlideModule in an application.\n *\n * <p>The output file generated by this class with a LibraryGlideModule looks like this:\n *\n * <pre>\n * <code>\n *  {@literal @com.bumptech.glide.annotation.compiler.Index(}\n *      modules = \"com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule\"\n *  )\n *  public class Indexer_GlideModule_com_bumptech_glide_integration_okhttp3_OkHttpLibraryGlideModule\n *  {\n *  }\n * </code>\n * </pre>\n *\n * <p>The output file generated by this class with a GlideExtension looks like this:\n *\n * <pre>\n * <code>\n *  {@literal @com.bumptech.glide.annotation.compiler.Index(}\n *      extensions = \"com.bumptech.glide.integration.gif.GifOptions\"\n *  )\n *  public class Indexer_GlideExtension_com_bumptech_glide_integration_gif_GifOptions {\n *  }\n * </code>\n * </pre>\n */\nfinal class IndexerGenerator {\n  private static final String INDEXER_NAME_PREFIX = \"GlideIndexer_\";\n  private static final int MAXIMUM_FILE_NAME_LENGTH = 255;\n  private final ProcessorUtil processorUtil;\n\n  IndexerGenerator(ProcessorUtil processorUtil) {\n    this.processorUtil = processorUtil;\n  }\n\n  TypeSpec generate(List<TypeElement> types) {\n    List<TypeElement> modules = new ArrayList<>();\n    List<TypeElement> extensions = new ArrayList<>();\n    for (TypeElement element : types) {\n      if (processorUtil.isExtension(element)) {\n        extensions.add(element);\n      } else if (processorUtil.isLibraryGlideModule(element)) {\n        modules.add(element);\n      } else {\n        throw new IllegalArgumentException(\"Unrecognized type: \" + element);\n      }\n    }\n    if (!modules.isEmpty() && !extensions.isEmpty()) {\n      throw new IllegalArgumentException(\n          \"Given both modules and extensions, expected one or the \"\n              + \"other. Modules: \"\n              + modules\n              + \" Extensions: \"\n              + extensions);\n    }\n    if (!modules.isEmpty()) {\n      return generate(types, GlideModule.class);\n    } else {\n      return generate(types, GlideExtension.class);\n    }\n  }\n\n  private TypeSpec generate(\n      List<TypeElement> libraryModules, Class<? extends Annotation> annotation) {\n    AnnotationSpec.Builder annotationBuilder = AnnotationSpec.builder(Index.class);\n\n    String value = getAnnotationValue(annotation);\n    for (TypeElement childModule : libraryModules) {\n      annotationBuilder.addMember(value, \"$S\", ClassName.get(childModule).toString());\n    }\n\n    StringBuilder indexerNameBuilder =\n        new StringBuilder(INDEXER_NAME_PREFIX + annotation.getSimpleName() + \"_\");\n    for (TypeElement element : libraryModules) {\n      indexerNameBuilder.append(element.getQualifiedName().toString().replace(\".\", \"_\"));\n      indexerNameBuilder.append(\"_\");\n    }\n    indexerNameBuilder =\n        new StringBuilder(indexerNameBuilder.substring(0, indexerNameBuilder.length() - 1));\n    String indexerName = indexerNameBuilder.toString();\n    // If the indexer name has too many packages/modules, it can exceed the file name length\n    // allowed by the file system, which can break compilation. To avoid that, fall back to a\n    // deterministic UUID.\n    if (indexerName.length() >= (MAXIMUM_FILE_NAME_LENGTH - INDEXER_NAME_PREFIX.length())) {\n      indexerName =\n          INDEXER_NAME_PREFIX\n              + UUID.nameUUIDFromBytes(indexerName.getBytes()).toString().replace(\"-\", \"_\");\n    }\n\n    return TypeSpec.classBuilder(indexerName)\n        .addAnnotation(annotationBuilder.build())\n        .addModifiers(Modifier.PUBLIC)\n        .build();\n  }\n\n  private static String getAnnotationValue(Class<? extends Annotation> annotation) {\n    if (annotation == GlideModule.class) {\n      return \"modules\";\n    } else if (annotation == GlideExtension.class) {\n      return \"extensions\";\n    } else {\n      throw new IllegalArgumentException(\"Unrecognized annotation: \" + annotation);\n    }\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/LibraryModuleProcessor.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.squareup.javapoet.TypeSpec;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.lang.model.element.TypeElement;\n\n/** Generates Indexer classes annotated with {@link Index} for all {@code LibraryGlideModule}s. */\nfinal class LibraryModuleProcessor {\n  private final ProcessorUtil processorUtil;\n  private final IndexerGenerator indexerGenerator;\n\n  LibraryModuleProcessor(ProcessorUtil processorUtil, IndexerGenerator indexerGenerator) {\n    this.processorUtil = processorUtil;\n    this.indexerGenerator = indexerGenerator;\n  }\n\n  boolean processModules(RoundEnvironment env) {\n    // Order matters here, if we find an Indexer below, we return before writing the root module.\n    // If we fail to add to appModules before then, we might accidentally skip a valid RootModule.\n    List<TypeElement> libraryGlideModules = new ArrayList<>();\n    for (TypeElement element : processorUtil.getElementsFor(GlideModule.class, env)) {\n      // Root elements are added separately and must be checked separately because they're sub\n      // classes of LibraryGlideModules.\n      if (processorUtil.isAppGlideModule(element)) {\n        continue;\n      } else if (!processorUtil.isLibraryGlideModule(element)) {\n        throw new IllegalStateException(\n            \"@GlideModule can only be applied to LibraryGlideModule\"\n                + \" and AppGlideModule implementations, not: \"\n                + element);\n      }\n\n      libraryGlideModules.add(element);\n    }\n\n    processorUtil.debugLog(\"got child modules: \" + libraryGlideModules);\n    if (libraryGlideModules.isEmpty()) {\n      return false;\n    }\n\n    TypeSpec indexer = indexerGenerator.generate(libraryGlideModules);\n    processorUtil.writeIndexer(indexer);\n    processorUtil.debugLog(\n        \"Wrote an Indexer this round, skipping the app module to ensure all \"\n            + \"indexers are found\");\n    // If I write an Indexer in a round in the target package, then try to find all classes in\n    // the target package, my newly written Indexer won't be found. Since we wrote a class with\n    // an Annotation handled by this processor, we know we will be called again in the next round\n    // and we can safely wait to write our AppGlideModule until then.\n    return true;\n  }\n\n  Set<String> getSupportedAnnotationTypes() {\n    return Collections.singleton(GlideModule.class.getName());\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/ProcessorUtil.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.GlideAnnotationProcessor.DEBUG;\n\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.google.common.base.Function;\nimport com.google.common.base.Joiner;\nimport com.google.common.base.Predicate;\nimport com.google.common.collect.FluentIterable;\nimport com.google.common.collect.ImmutableBiMap;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.Lists;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.CodeBlock;\nimport com.squareup.javapoet.JavaFile;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.TypeName;\nimport com.squareup.javapoet.TypeSpec;\nimport com.squareup.javapoet.TypeVariableName;\nimport java.lang.annotation.Annotation;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.lang.model.element.AnnotationMirror;\nimport javax.lang.model.element.AnnotationValue;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.ElementKind;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.Name;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.element.TypeParameterElement;\nimport javax.lang.model.element.VariableElement;\nimport javax.lang.model.type.DeclaredType;\nimport javax.lang.model.type.TypeKind;\nimport javax.lang.model.type.TypeMirror;\nimport javax.lang.model.type.TypeVariable;\nimport javax.lang.model.util.ElementFilter;\nimport javax.lang.model.util.Elements;\nimport javax.lang.model.util.Types;\nimport javax.tools.Diagnostic;\n\n/** Utilities for writing classes and logging. */\nfinal class ProcessorUtil {\n  private static final String GLIDE_MODULE_PACKAGE_NAME = \"com.bumptech.glide.module\";\n  private static final String APP_GLIDE_MODULE_SIMPLE_NAME = \"AppGlideModule\";\n  private static final String LIBRARY_GLIDE_MODULE_SIMPLE_NAME = \"LibraryGlideModule\";\n  private static final String APP_GLIDE_MODULE_QUALIFIED_NAME =\n      GLIDE_MODULE_PACKAGE_NAME + \".\" + APP_GLIDE_MODULE_SIMPLE_NAME;\n  private static final String LIBRARY_GLIDE_MODULE_QUALIFIED_NAME =\n      GLIDE_MODULE_PACKAGE_NAME + \".\" + LIBRARY_GLIDE_MODULE_SIMPLE_NAME;\n  private static final String COMPILER_PACKAGE_NAME =\n      GlideAnnotationProcessor.class.getPackage().getName();\n  private static final ClassName SUPPORT_NONNULL_ANNOTATION =\n      ClassName.get(\"android.support.annotation\", \"NonNull\");\n  private static final ClassName JETBRAINS_NOTNULL_ANNOTATION =\n      ClassName.get(\"org.jetbrains.annotations\", \"NotNull\");\n  private static final ClassName ANDROIDX_NONNULL_ANNOTATION =\n      ClassName.get(\"androidx.annotation\", \"NonNull\");\n  private static final ClassName SUPPORT_CHECK_RESULT_ANNOTATION =\n      ClassName.get(\"android.support.annotation\", \"CheckResult\");\n  private static final ClassName ANDROIDX_CHECK_RESULT_ANNOTATION =\n      ClassName.get(\"androidx.annotation\", \"CheckResult\");\n  private static final ClassName SUPPORT_VISIBLE_FOR_TESTING =\n      ClassName.get(\"android.support.annotation\", \"VisibleForTesting\");\n  private static final ClassName ANDROIDX_VISIBLE_FOR_TESTING =\n      ClassName.get(\"androidx.annotation\", \"VisibleForTesting\");\n\n  private final ProcessingEnvironment processingEnv;\n  private final TypeElement appGlideModuleType;\n  private final TypeElement libraryGlideModuleType;\n  private int round;\n\n  ProcessorUtil(ProcessingEnvironment processingEnv) {\n    this.processingEnv = processingEnv;\n\n    appGlideModuleType =\n        processingEnv.getElementUtils().getTypeElement(APP_GLIDE_MODULE_QUALIFIED_NAME);\n    libraryGlideModuleType =\n        processingEnv.getElementUtils().getTypeElement(LIBRARY_GLIDE_MODULE_QUALIFIED_NAME);\n  }\n\n  void process() {\n    round++;\n  }\n\n  boolean isAppGlideModule(TypeElement element) {\n    return processingEnv.getTypeUtils().isAssignable(element.asType(), appGlideModuleType.asType());\n  }\n\n  boolean isLibraryGlideModule(TypeElement element) {\n    return processingEnv\n        .getTypeUtils()\n        .isAssignable(element.asType(), libraryGlideModuleType.asType());\n  }\n\n  boolean isExtension(TypeElement element) {\n    return element.getAnnotation(GlideExtension.class) != null;\n  }\n\n  int getOverrideType(ExecutableElement element) {\n    GlideOption glideOption = element.getAnnotation(GlideOption.class);\n    return glideOption.override();\n  }\n\n  void writeIndexer(TypeSpec indexer) {\n    writeClass(COMPILER_PACKAGE_NAME, indexer);\n  }\n\n  void writeClass(String packageName, TypeSpec clazz) {\n    try {\n      debugLog(\"Writing class:\\n\" + clazz);\n      JavaFile.builder(packageName, clazz)\n          .skipJavaLangImports(true)\n          .build()\n          .writeTo(processingEnv.getFiler());\n    } catch (Throwable e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  List<ExecutableElement> findAnnotatedElementsInClasses(\n      Set<String> classNames, Class<? extends Annotation> annotationClass) {\n    List<ExecutableElement> result = new ArrayList<>();\n    for (String glideExtensionClassName : classNames) {\n      TypeElement glideExtension =\n          processingEnv.getElementUtils().getTypeElement(glideExtensionClassName);\n      for (Element element : glideExtension.getEnclosedElements()) {\n        if (element.getAnnotation(annotationClass) != null) {\n          result.add((ExecutableElement) element);\n        }\n      }\n    }\n    return result;\n  }\n\n  List<TypeElement> getElementsFor(Class<? extends Annotation> clazz, RoundEnvironment env) {\n    Collection<? extends Element> annotatedElements = env.getElementsAnnotatedWith(clazz);\n    return ElementFilter.typesIn(annotatedElements);\n  }\n\n  /**\n   * Generates a Javadoc code block for generated methods that delegate to methods in {@link\n   * GlideExtension}s.\n   *\n   * <p>The generated block looks something like this:\n   *\n   * <pre>\n   * <code>\n   *   {@literal @see} com.extension.package.name.ExtensionClassName#extensionMethod(arg1, argN)\n   * </code>\n   * </pre>\n   *\n   * @param method The method from the {@link GlideExtension} annotated class that the generated\n   *     method this Javadoc will be attached to delegates to.\n   */\n  CodeBlock generateSeeMethodJavadoc(ExecutableElement method) {\n    // Use the simple name of the containing type instead of just the containing type's TypeMirror\n    // so that we avoid appending <CHILD> or other type arguments to the class and breaking\n    // Javadoc's linking.\n    // With this we get @see RequestOptions#methodName().\n    // With just ClassName.get(element.getEnclosingElement().asType()), we get:\n    // @see RequestOptions<CHILD>#methodName().\n    return generateSeeMethodJavadoc(\n        getJavadocSafeName(method.getEnclosingElement()),\n        method.getSimpleName().toString(),\n        method.getParameters());\n  }\n\n  /**\n   * Generates a Javadoc block for generated methods that delegate to other methods.\n   *\n   * <p>The generated block looks something like this:\n   *\n   * <pre>\n   * <code>\n   *     {@literal @see} com.package.ClassContainingMethod.methodSimpleName(\n   *         methodParam1, methodParamN)\n   * </code>\n   * </pre>\n   *\n   * @param nameOfClassContainingMethod The simple class name of the class containing the method\n   *     without any generic types like {@literal <T>}.\n   * @param methodSimpleName The name of the method.\n   * @param methodParameters A maybe empty list of all the parameters for the method in question.\n   */\n  CodeBlock generateSeeMethodJavadoc(\n      TypeName nameOfClassContainingMethod,\n      String methodSimpleName,\n      List<? extends VariableElement> methodParameters) {\n    return generateSeeMethodJavadocInternal(\n        nameOfClassContainingMethod,\n        methodSimpleName,\n        Lists.transform(\n            methodParameters,\n            new Function<VariableElement, Object>() {\n              @Override\n              public Object apply(VariableElement input) {\n                return getJavadocSafeName(input);\n              }\n            }));\n  }\n\n  CodeBlock generateSeeMethodJavadoc(TypeName nameOfClassContainingMethod, MethodSpec methodSpec) {\n    return generateSeeMethodJavadocInternal(\n        nameOfClassContainingMethod,\n        methodSpec.name,\n        Lists.transform(\n            methodSpec.parameters,\n            new Function<ParameterSpec, Object>() {\n              @Override\n              public Object apply(ParameterSpec input) {\n                return input.type;\n              }\n            }));\n  }\n\n  private CodeBlock generateSeeMethodJavadocInternal(\n      TypeName nameOfClassContainingMethod, String methodName, List<Object> safeParameterNames) {\n    StringBuilder javadocString = new StringBuilder(\"@see $T#$L(\");\n    List<Object> javadocArgs = new ArrayList<>();\n    javadocArgs.add(nameOfClassContainingMethod);\n    javadocArgs.add(methodName);\n\n    for (Object param : safeParameterNames) {\n      javadocString.append(\"$T, \");\n      javadocArgs.add(param);\n    }\n    if (javadocArgs.size() > 2) {\n      javadocString = new StringBuilder(javadocString.substring(0, javadocString.length() - 2));\n    }\n    javadocString.append(\")\\n\");\n    return CodeBlock.of(javadocString.toString(), javadocArgs.toArray(new Object[0]));\n  }\n\n  /**\n   * Returns a safe String to use in a Javadoc that will function in a link.\n   *\n   * <p>This method exists because by Javadoc doesn't handle type parameters({@literal <T>} in\n   * {@literal RequestOptions<T>} for example).\n   */\n  private TypeName getJavadocSafeName(Element element) {\n    Types typeUtils = processingEnv.getTypeUtils();\n    TypeMirror type = element.asType();\n    if (typeUtils.asElement(type) == null) {\n      // If there is no Element, it's a primitive and can't have additional types, so we're done.\n      return ClassName.get(element.asType());\n    }\n    Name simpleName = typeUtils.asElement(type).getSimpleName();\n    return ClassName.bestGuess(simpleName.toString());\n  }\n\n  void debugLog(String toLog) {\n    if (DEBUG) {\n      infoLog(toLog);\n    }\n  }\n\n  void infoLog(String toLog) {\n    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, \"[\" + round + \"] \" + toLog);\n  }\n\n  static CodeBlock generateCastingSuperCall(TypeName toReturn, MethodSpec method) {\n    return CodeBlock.builder()\n        .add(\"return ($T) super.$N(\", toReturn, method.name)\n        .add(\n            FluentIterable.from(method.parameters)\n                .transform(\n                    new Function<ParameterSpec, String>() {\n                      @Override\n                      public String apply(ParameterSpec input) {\n                        return input.name;\n                      }\n                    })\n                .join(Joiner.on(\",\")))\n        .add(\");\\n\")\n        .build();\n  }\n\n  MethodSpec.Builder overriding(ExecutableElement method) {\n    String methodName = method.getSimpleName().toString();\n\n    MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName).addAnnotation(Override.class);\n\n    Set<Modifier> modifiers = method.getModifiers();\n    modifiers = new LinkedHashSet<>(modifiers);\n    modifiers.remove(Modifier.ABSTRACT);\n    Modifier defaultModifier = null;\n    // Modifier.DEFAULT doesn't exist until Java 8.\n    try {\n      defaultModifier = Modifier.valueOf(\"DEFAULT\");\n    } catch (IllegalArgumentException e) {\n      // Ignored.\n    }\n    modifiers.remove(defaultModifier);\n\n    builder = builder.addModifiers(modifiers);\n\n    for (TypeParameterElement typeParameterElement : method.getTypeParameters()) {\n      TypeVariable var = (TypeVariable) typeParameterElement.asType();\n      builder = builder.addTypeVariable(TypeVariableName.get(var));\n    }\n\n    builder =\n        builder\n            .returns(TypeName.get(method.getReturnType()))\n            .addParameters(getParameters(method))\n            .varargs(method.isVarArgs());\n\n    for (TypeMirror thrownType : method.getThrownTypes()) {\n      builder = builder.addException(TypeName.get(thrownType));\n    }\n\n    return builder;\n  }\n\n  List<ParameterSpec> getParameters(ExecutableElement method) {\n    return getParameters(method.getParameters());\n  }\n\n  List<ParameterSpec> getParameters(List<? extends VariableElement> parameters) {\n    List<ParameterSpec> result = new ArrayList<>();\n    for (VariableElement parameter : parameters) {\n      result.add(getParameter(parameter));\n    }\n    return dedupedParameters(result);\n  }\n\n  private static List<ParameterSpec> dedupedParameters(List<ParameterSpec> parameters) {\n    boolean hasDupes = false;\n    Set<String> names = new HashSet<>();\n    for (ParameterSpec parameter : parameters) {\n      String name = parameter.name;\n      if (names.contains(name)) {\n        hasDupes = true;\n      } else {\n        names.add(name);\n      }\n    }\n\n    if (hasDupes) {\n      List<ParameterSpec> copy = parameters;\n      parameters = new ArrayList<>();\n      for (int i = 0; i < copy.size(); i++) {\n        ParameterSpec parameter = copy.get(i);\n        parameters.add(\n            ParameterSpec.builder(parameter.type, parameter.name + i)\n                .addModifiers(parameter.modifiers)\n                .addAnnotations(parameter.annotations)\n                .build());\n      }\n    }\n\n    return parameters;\n  }\n\n  private ParameterSpec getParameter(VariableElement parameter) {\n    TypeName type = TypeName.get(parameter.asType());\n    return ParameterSpec.builder(type, computeParameterName(parameter, type))\n        .addModifiers(parameter.getModifiers())\n        .addAnnotations(getAnnotations(parameter))\n        .build();\n  }\n\n  private static String computeParameterName(VariableElement parameter, TypeName type) {\n    String rawClassName = type.withoutAnnotations().toString();\n\n    String name;\n\n    if (type.isPrimitive() || type.isBoxedPrimitive()) {\n      name = getSmartPrimitiveParameterName(parameter);\n    } else {\n      if (rawClassName.contains(\"<\") && rawClassName.contains(\">\")) {\n        String[] preGenericSplit = rawClassName.split(\"<\");\n        String preGeneric = preGenericSplit[0];\n        String[] postGenericSplit = rawClassName.split(\">\");\n        String postGeneric = postGenericSplit[postGenericSplit.length - 1];\n        if (postGenericSplit.length > 1) {\n          rawClassName = preGeneric + postGeneric;\n        } else {\n          rawClassName = preGeneric;\n        }\n      }\n\n      String[] qualifiers = rawClassName.split(\"\\\\.\");\n      rawClassName = qualifiers[qualifiers.length - 1];\n\n      rawClassName = applySmartParameterNameReplacements(rawClassName);\n\n      boolean allCaps = true;\n      for (char c : rawClassName.toCharArray()) {\n        if (Character.isLowerCase(c)) {\n          allCaps = false;\n          break;\n        }\n      }\n      if (allCaps) {\n        name = rawClassName.toLowerCase(Locale.ROOT);\n      } else {\n        int indexOfLastWordStart = 0;\n        char[] chars = rawClassName.toCharArray();\n        for (int i = 0, charArrayLength = chars.length; i < charArrayLength; i++) {\n          char c = chars[i];\n          if (Character.isUpperCase(c)) {\n            indexOfLastWordStart = i;\n          }\n        }\n        rawClassName = rawClassName.substring(indexOfLastWordStart, rawClassName.length());\n\n        name =\n            Character.toLowerCase(rawClassName.charAt(0))\n                + rawClassName.substring(1, rawClassName.length());\n      }\n    }\n\n    return name;\n  }\n\n  private static String getSmartPrimitiveParameterName(VariableElement parameter) {\n    for (AnnotationMirror annotation : parameter.getAnnotationMirrors()) {\n      String annotationName = annotation.getAnnotationType().toString().toUpperCase(Locale.ROOT);\n      if (annotationName.endsWith(\"RES\")) {\n        // Catch annotations like StringRes\n        return \"id\";\n      } else if (annotationName.endsWith(\"RANGE\")) {\n        // Catch annotations like IntRange\n        return \"value\";\n      }\n    }\n\n    return parameter.getSimpleName().toString();\n  }\n\n  private static String applySmartParameterNameReplacements(String name) {\n    name = name.replace(\"[]\", \"s\");\n    name = name.replace(Class.class.getSimpleName(), \"clazz\");\n    name = name.replace(Object.class.getSimpleName(), \"o\");\n    return name;\n  }\n\n  private List<AnnotationSpec> getAnnotations(VariableElement element) {\n    List<AnnotationSpec> result = new ArrayList<>();\n    for (AnnotationMirror mirror : element.getAnnotationMirrors()) {\n      result.add(maybeConvertSupportLibraryAnnotation(mirror));\n    }\n    return result;\n  }\n\n  private AnnotationSpec maybeConvertSupportLibraryAnnotation(AnnotationMirror mirror) {\n    String annotationName = mirror.getAnnotationType().asElement().toString();\n    boolean preferAndroidX = visibleForTesting().equals(ANDROIDX_VISIBLE_FOR_TESTING);\n    ImmutableBiMap<ClassName, ClassName> map =\n        ImmutableBiMap.<ClassName, ClassName>builder()\n            .put(SUPPORT_NONNULL_ANNOTATION, ANDROIDX_NONNULL_ANNOTATION)\n            .put(SUPPORT_CHECK_RESULT_ANNOTATION, ANDROIDX_CHECK_RESULT_ANNOTATION)\n            .put(SUPPORT_VISIBLE_FOR_TESTING, ANDROIDX_VISIBLE_FOR_TESTING)\n            .build();\n\n    ClassName remapped = null;\n    if (preferAndroidX && annotationName.startsWith(\"android.support.annotation\")) {\n      remapped = ClassName.get((TypeElement) mirror.getAnnotationType().asElement());\n    } else if (!preferAndroidX && annotationName.startsWith(\"androidx.annotation\")) {\n      remapped = ClassName.get((TypeElement) mirror.getAnnotationType().asElement());\n    }\n    if (remapped != null && map.containsKey(remapped)) {\n      return AnnotationSpec.builder(map.get(remapped)).build();\n    } else {\n      return AnnotationSpec.get(mirror);\n    }\n  }\n\n  ClassName visibleForTesting() {\n    return findAnnotationClassName(ANDROIDX_VISIBLE_FOR_TESTING, SUPPORT_VISIBLE_FOR_TESTING);\n  }\n\n  ClassName nonNull() {\n    return findAnnotationClassName(ANDROIDX_NONNULL_ANNOTATION, SUPPORT_NONNULL_ANNOTATION);\n  }\n\n  ClassName checkResult() {\n    return findAnnotationClassName(\n        ANDROIDX_CHECK_RESULT_ANNOTATION, SUPPORT_CHECK_RESULT_ANNOTATION);\n  }\n\n  static List<ClassName> nonNulls() {\n    return ImmutableList.of(\n        SUPPORT_NONNULL_ANNOTATION, JETBRAINS_NOTNULL_ANNOTATION, ANDROIDX_NONNULL_ANNOTATION);\n  }\n\n  private ClassName findAnnotationClassName(ClassName androidxName, ClassName supportName) {\n    Elements elements = processingEnv.getElementUtils();\n    TypeElement visibleForTestingTypeElement =\n        elements.getTypeElement(androidxName.reflectionName());\n    if (visibleForTestingTypeElement != null) {\n      return androidxName;\n    }\n\n    return supportName;\n  }\n\n  List<ExecutableElement> findInstanceMethodsReturning(TypeElement clazz, TypeMirror returnType) {\n    return FluentIterable.from(clazz.getEnclosedElements())\n        .filter(new FilterPublicMethods(returnType, MethodType.INSTANCE))\n        .transform(new ToMethod())\n        .toList();\n  }\n\n  List<ExecutableElement> findInstanceMethodsReturning(TypeElement clazz, TypeElement returnType) {\n    return FluentIterable.from(clazz.getEnclosedElements())\n        .filter(new FilterPublicMethods(returnType, MethodType.INSTANCE))\n        .transform(new ToMethod())\n        .toList();\n  }\n\n  List<ExecutableElement> findStaticMethodsReturning(TypeElement clazz, TypeElement returnType) {\n    return FluentIterable.from(clazz.getEnclosedElements())\n        .filter(new FilterPublicMethods(returnType, MethodType.STATIC))\n        .transform(new ToMethod())\n        .toList();\n  }\n\n  List<ExecutableElement> findStaticMethods(TypeElement clazz) {\n    return FluentIterable.from(clazz.getEnclosedElements())\n        .filter(new FilterPublicMethods((TypeMirror) null /*returnType*/, MethodType.STATIC))\n        .transform(new ToMethod())\n        .toList();\n  }\n\n  ImmutableSet<String> findClassValuesFromAnnotationOnClassAsNames(\n      Element clazz, Class<? extends Annotation> annotationClass) {\n    String annotationClassName = annotationClass.getName();\n    AnnotationValue excludedModuleAnnotationValue = null;\n    for (AnnotationMirror annotationMirror : clazz.getAnnotationMirrors()) {\n      // Two different AnnotationMirrors the same class might not be equal, so compare Strings\n      // instead. This check is necessary because a given class may have multiple Annotations.\n      if (!annotationClassName.equals(annotationMirror.getAnnotationType().toString())) {\n        continue;\n      }\n\n      var entries = annotationMirror.getElementValues().entrySet();\n      if (entries.size() != 1) {\n        throw new IllegalArgumentException(\"Expected single value, but found: \" + entries);\n      }\n      excludedModuleAnnotationValue = entries.iterator().next().getValue();\n      if (excludedModuleAnnotationValue == null) {\n        throw new IllegalArgumentException(\n            \"Failed to find value for: \"\n                + annotationClass\n                + \" from mirrors: \"\n                + clazz.getAnnotationMirrors());\n      }\n    }\n\n    if (excludedModuleAnnotationValue == null) {\n      return ImmutableSet.of();\n    }\n\n    Object value = excludedModuleAnnotationValue.getValue();\n    if (value instanceof List) {\n      LinkedHashSet<String> out = new LinkedHashSet<>();\n      for (Object o : (List<?>) value) {\n        AnnotationValue av = (AnnotationValue) o;\n        out.add(qualifiedNameFromTypeMirror((TypeMirror) av.getValue()));\n      }\n      return ImmutableSet.copyOf(out);\n    } else {\n      return ImmutableSet.of(qualifiedNameFromTypeMirror((TypeMirror) value));\n    }\n  }\n\n  static String qualifiedNameFromTypeMirror(TypeMirror type) {\n    if (type.getKind() == TypeKind.ERROR) {\n      throw new IllegalArgumentException(\"Unresolved class type in annotation: \" + type);\n    }\n    if (type.getKind() == TypeKind.DECLARED) {\n      DeclaredType dt = (DeclaredType) type;\n      TypeElement te = (TypeElement) dt.asElement();\n      return te.getQualifiedName().toString();\n    }\n    return type.toString();\n  }\n\n  private enum MethodType {\n    STATIC,\n    INSTANCE\n  }\n\n  private final class FilterPublicMethods implements Predicate<Element> {\n    @Nullable private final TypeMirror returnType;\n    private final MethodType methodType;\n\n    FilterPublicMethods(@Nullable TypeMirror returnType, MethodType methodType) {\n      this.returnType = returnType;\n      this.methodType = methodType;\n    }\n\n    FilterPublicMethods(@Nullable TypeElement returnType, MethodType methodType) {\n      this(returnType != null ? returnType.asType() : null, methodType);\n    }\n\n    @Override\n    public boolean apply(@Nullable Element input) {\n      if (input == null\n          || input.getKind() != ElementKind.METHOD\n          || !input.getModifiers().contains(Modifier.PUBLIC)) {\n        return false;\n      }\n      boolean isStatic = input.getModifiers().contains(Modifier.STATIC);\n      if (methodType == MethodType.STATIC && !isStatic) {\n        return false;\n      } else if (methodType == MethodType.INSTANCE && isStatic) {\n        return false;\n      }\n      ExecutableElement method = (ExecutableElement) input;\n      return returnType == null || isReturnValueTypeMatching(method, returnType);\n    }\n  }\n\n  boolean isReturnValueTypeMatching(ExecutableElement method, TypeElement expectedReturnType) {\n    return isReturnValueTypeMatching(method, expectedReturnType.asType());\n  }\n\n  private boolean isReturnValueTypeMatching(\n      ExecutableElement method, TypeMirror expectedReturnType) {\n    return processingEnv.getTypeUtils().isAssignable(method.getReturnType(), expectedReturnType);\n  }\n\n  private static final class ToMethod implements Function<Element, ExecutableElement> {\n\n    @Nullable\n    @Override\n    public ExecutableElement apply(@Nullable Element input) {\n      return (ExecutableElement) input;\n    }\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/RequestBuilderGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.google.common.base.Function;\nimport com.google.common.base.Joiner;\nimport com.google.common.base.Predicate;\nimport com.google.common.collect.FluentIterable;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.Lists;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.CodeBlock;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.ParameterizedTypeName;\nimport com.squareup.javapoet.TypeName;\nimport com.squareup.javapoet.TypeSpec;\nimport com.squareup.javapoet.TypeVariableName;\nimport com.squareup.javapoet.WildcardTypeName;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.AnnotationMirror;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.type.DeclaredType;\nimport javax.lang.model.type.TypeMirror;\n\n/**\n * Generates a {@code com.bumptech.glide.RequestBuilder} subclass containing all methods from the\n * base class, all methods from {@code com.bumptech.glide.request.RequestOptions} and all\n * non-override {@link GlideOption} annotated methods in {@link GlideExtension} annotated classes.\n *\n * <p>Generated code looks like this:\n *\n * <pre>\n * <code>\n * public final class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> {\n *   GlideRequest(Class<TranscodeType> transcodeClass, RequestBuilder<?> other) {\n *     super(transcodeClass, other);\n *   }\n *\n *   GlideRequest(GlideContext context, RequestManager requestManager,\n *       Class<TranscodeType> transcodeClass) {\n *     super(context, requestManager ,transcodeClass);\n *   }\n *\n *   {@literal @Override}\n *   protected GlideRequest<File> getDownloadOnlyRequest() {\n *    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n *   }\n *\n *   /**\n *    * {@literal @see} GlideOptions#dontAnimate()\n *    *\\/\n *   public GlideRequest<TranscodeType> dontAnimate() {\n *     if (getMutableOptions() instanceof GlideOptions) {\n *       this.requestOptions = ((GlideOptions) getMutableOptions()).dontAnimate();\n *     } else {\n *       this.requestOptions = new GlideOptions().apply(this.requestOptions).dontAnimate();\n *     }\n *     return this;\n *   }\n *\n *   /**\n *    * {@literal @see} RequestOptions#sizeMultiplier(float)\n *    *\\/\n *   public GlideRequest<TranscodeType> sizeMultiplier(float sizeMultiplier) {\n *     this.requestOptions = getMutableOptions().sizeMultiplier(sizeMultiplier);\n *     return this;\n *   }\n *\n *   ...\n * }\n * </code>\n * </pre>\n */\nfinal class RequestBuilderGenerator {\n  private static final String REQUEST_OPTIONS_PACKAGE_NAME = \"com.bumptech.glide.request\";\n  private static final String REQUEST_OPTIONS_SIMPLE_NAME = \"RequestOptions\";\n  private static final String REQUEST_OPTIONS_QUALIFIED_NAME =\n      REQUEST_OPTIONS_PACKAGE_NAME + \".\" + REQUEST_OPTIONS_SIMPLE_NAME;\n\n  private static final String REQUEST_BUILDER_PACKAGE_NAME = \"com.bumptech.glide\";\n  private static final String REQUEST_BUILDER_SIMPLE_NAME = \"RequestBuilder\";\n  static final String REQUEST_BUILDER_QUALIFIED_NAME =\n      REQUEST_BUILDER_PACKAGE_NAME + \".\" + REQUEST_BUILDER_SIMPLE_NAME;\n\n  // Uses package private methods and variables.\n  private static final String GENERATED_REQUEST_BUILDER_SIMPLE_NAME = \"GlideRequest\";\n\n  /**\n   * An arbitrary name of the Generic type in the generated RequestBuilder. e.g.\n   * RequestBuilder<TranscodeType>\n   */\n  private static final String TRANSCODE_TYPE_NAME = \"TranscodeType\";\n\n  /** A set of method names to avoid overriding from RequestOptions. */\n  private static final ImmutableSet<String> EXCLUDED_METHODS_FROM_BASE_REQUEST_OPTIONS =\n      ImmutableSet.of(\"clone\", \"apply\");\n\n  private final ProcessingEnvironment processingEnv;\n  private final ProcessorUtil processorUtil;\n  private final TypeVariableName transcodeTypeName;\n  private final TypeElement requestOptionsType;\n  private final TypeElement requestBuilderType;\n  private ClassName generatedRequestBuilderClassName;\n  private ClassName requestOptionsClassName;\n  private ParameterizedTypeName generatedRequestBuilderOfTranscodeType;\n\n  RequestBuilderGenerator(ProcessingEnvironment processingEnv, ProcessorUtil processorUtil) {\n    this.processingEnv = processingEnv;\n    this.processorUtil = processorUtil;\n\n    requestBuilderType =\n        processingEnv.getElementUtils().getTypeElement(REQUEST_BUILDER_QUALIFIED_NAME);\n\n    transcodeTypeName = TypeVariableName.get(TRANSCODE_TYPE_NAME);\n\n    requestOptionsType =\n        processingEnv.getElementUtils().getTypeElement(REQUEST_OPTIONS_QUALIFIED_NAME);\n  }\n\n  TypeSpec generate(\n      String generatedCodePackageName,\n      Set<String> glideExtensionClassNames,\n      @Nullable TypeSpec generatedOptions) {\n    if (generatedOptions != null) {\n      requestOptionsClassName = ClassName.get(generatedCodePackageName, generatedOptions.name);\n    } else {\n      requestOptionsClassName =\n          ClassName.get(\n              RequestOptionsGenerator.REQUEST_OPTIONS_PACKAGE_NAME,\n              RequestOptionsGenerator.BASE_REQUEST_OPTIONS_SIMPLE_NAME);\n    }\n\n    generatedRequestBuilderClassName =\n        ClassName.get(generatedCodePackageName, GENERATED_REQUEST_BUILDER_SIMPLE_NAME);\n    generatedRequestBuilderOfTranscodeType =\n        ParameterizedTypeName.get(generatedRequestBuilderClassName, transcodeTypeName);\n    RequestOptionsExtensionGenerator requestOptionsExtensionGenerator =\n        new RequestOptionsExtensionGenerator(generatedRequestBuilderOfTranscodeType, processorUtil);\n\n    ParameterizedTypeName requestBuilderOfTranscodeType =\n        ParameterizedTypeName.get(\n            ClassName.get(REQUEST_BUILDER_PACKAGE_NAME, REQUEST_BUILDER_SIMPLE_NAME),\n            transcodeTypeName);\n\n    List<MethodSpec> requestOptionsExtensionMethods =\n        requestOptionsExtensionGenerator.generateInstanceMethodsForExtensions(\n            glideExtensionClassNames);\n\n    return TypeSpec.classBuilder(GENERATED_REQUEST_BUILDER_SIMPLE_NAME)\n        .addJavadoc(\n            \"Contains all public methods from {@link $T}, all options from\\n\", requestBuilderType)\n        .addJavadoc(\"{@link $T} and all generated options from\\n\", requestOptionsType)\n        .addJavadoc(\"{@link $T} in annotated methods in\\n\", GlideOption.class)\n        .addJavadoc(\"{@link $T} annotated classes.\\n\", GlideExtension.class)\n        .addJavadoc(\"\\n\")\n        .addJavadoc(\"<p>Generated code, do not modify.\\n\")\n        .addJavadoc(\"\\n\")\n        .addJavadoc(\"@see $T\\n\", requestBuilderType)\n        .addJavadoc(\"@see $T\\n\", requestOptionsType)\n        .addAnnotation(\n            AnnotationSpec.builder(SuppressWarnings.class)\n                .addMember(\"value\", \"$S\", \"unused\")\n                .addMember(\"value\", \"$S\", \"deprecation\")\n                .build())\n        .addModifiers(Modifier.PUBLIC)\n        .addTypeVariable(transcodeTypeName)\n        .superclass(requestBuilderOfTranscodeType)\n        .addSuperinterface(Cloneable.class)\n        .addMethods(generateConstructors())\n        .addMethod(generateDownloadOnlyRequestMethod())\n        .addMethods(\n            generateGeneratedRequestOptionsEquivalents(\n                requestOptionsExtensionMethods, generatedOptions))\n        .addMethods(generateRequestBuilderOverrides())\n        .addMethods(requestOptionsExtensionMethods)\n        .build();\n  }\n\n  /**\n   * Generates methods with equivalent names and arguments to methods annotated with {@link\n   * GlideOption} in {@link com.bumptech.glide.annotation.GlideExtension}s that return our generated\n   * {@code com.bumptech.glide.RequestBuilder} subclass.\n   */\n  private List<MethodSpec> generateGeneratedRequestOptionsEquivalents(\n      final List<MethodSpec> requestOptionsExtensionMethods,\n      @Nullable final TypeSpec generatedOptions) {\n    if (generatedOptions == null) {\n      return Collections.emptyList();\n    }\n    return FluentIterable.from(generatedOptions.methodSpecs)\n        .filter(\n            new Predicate<MethodSpec>() {\n              @Override\n              public boolean apply(MethodSpec input) {\n                return isUsefulGeneratedRequestOption(requestOptionsExtensionMethods, input);\n              }\n            })\n        .transform(\n            new Function<MethodSpec, MethodSpec>() {\n              @Override\n              public MethodSpec apply(MethodSpec input) {\n                return generateGeneratedRequestOptionEquivalent(input);\n              }\n            })\n        .toList();\n  }\n\n  /**\n   * Returns {@code true} if the given {@link MethodSpec} is a useful method to have in our {@code\n   * com.bumptech.glide.RequestBuilder} subclass.\n   *\n   * <p>Only newly generated methods will be included in the generated {@code\n   * com.bumptech.glide.request.BaseRequestBuilder} subclass, so we only have to filter out methods\n   * that override other methods to avoid duplicates.\n   */\n  private boolean isUsefulGeneratedRequestOption(\n      List<MethodSpec> requestOptionsExtensionMethods, final MethodSpec requestOptionsMethod) {\n    return !EXCLUDED_METHODS_FROM_BASE_REQUEST_OPTIONS.contains(requestOptionsMethod.name)\n        && requestOptionsMethod.hasModifier(Modifier.PUBLIC)\n        && !requestOptionsMethod.hasModifier(Modifier.STATIC)\n        && requestOptionsMethod.returnType.toString().equals(requestOptionsClassName.toString())\n        && !isExtensionMethod(requestOptionsExtensionMethods, requestOptionsMethod);\n  }\n\n  private boolean isExtensionMethod(\n      List<MethodSpec> requestOptionsExtensionMethods, final MethodSpec requestOptionsMethod) {\n    return FluentIterable.from(requestOptionsExtensionMethods)\n        .anyMatch(\n            new Predicate<MethodSpec>() {\n              @Override\n              public boolean apply(MethodSpec input) {\n                return input.name.equals(requestOptionsMethod.name)\n                    && input.parameters.equals(requestOptionsMethod.parameters);\n              }\n            });\n  }\n\n  /**\n   * Generates a particular method with an equivalent name and arguments to the given method from\n   * the generated {@code com.bumptech.glide.request.BaseRequestBuilder} subclass.\n   */\n  private MethodSpec generateGeneratedRequestOptionEquivalent(MethodSpec requestOptionMethod) {\n    CodeBlock callRequestOptionsMethod =\n        CodeBlock.builder()\n            .add(\".$N(\", requestOptionMethod.name)\n            .add(\n                FluentIterable.from(requestOptionMethod.parameters)\n                    .transform(\n                        new Function<ParameterSpec, String>() {\n                          @Override\n                          public String apply(ParameterSpec input) {\n                            return input.name;\n                          }\n                        })\n                    .join(Joiner.on(\", \")))\n            .add(\");\\n\")\n            .build();\n\n    MethodSpec.Builder result =\n        MethodSpec.methodBuilder(requestOptionMethod.name)\n            .addJavadoc(\n                processorUtil.generateSeeMethodJavadoc(\n                    requestOptionsClassName, requestOptionMethod))\n            .addModifiers(Modifier.PUBLIC)\n            .varargs(requestOptionMethod.varargs)\n            .addAnnotations(\n                FluentIterable.from(requestOptionMethod.annotations)\n                    .filter(\n                        new Predicate<AnnotationSpec>() {\n                          @Override\n                          public boolean apply(AnnotationSpec input) {\n                            return !input.type.equals(TypeName.get(Override.class))\n                                // SafeVarargs can only be applied to final methods. GlideRequest is\n                                // non-final to allow for mocking.\n                                && !input.type.equals(TypeName.get(SafeVarargs.class))\n                                // We need to combine warnings below.\n                                && !input.type.equals(TypeName.get(SuppressWarnings.class));\n                          }\n                        })\n                    .toList())\n            .addTypeVariables(requestOptionMethod.typeVariables)\n            .addParameters(requestOptionMethod.parameters)\n            .returns(generatedRequestBuilderOfTranscodeType)\n            .addCode(\"return ($T) super\", generatedRequestBuilderOfTranscodeType)\n            .addCode(callRequestOptionsMethod);\n\n    AnnotationSpec suppressWarnings = buildSuppressWarnings(requestOptionMethod);\n    if (suppressWarnings != null) {\n      result.addAnnotation(suppressWarnings);\n    }\n    return result.build();\n  }\n\n  @Nullable\n  private AnnotationSpec buildSuppressWarnings(MethodSpec requestOptionMethod) {\n    Set<String> suppressions = new HashSet<>();\n    if (requestOptionMethod.annotations.contains(\n        AnnotationSpec.builder(SuppressWarnings.class).build())) {\n      for (AnnotationSpec annotation : requestOptionMethod.annotations) {\n        if (annotation.type.equals(TypeName.get(SuppressWarnings.class))) {\n          List<CodeBlock> codeBlocks = annotation.members.get(\"value\");\n          suppressions.addAll(\n              FluentIterable.from(codeBlocks)\n                  .transform(\n                      new Function<CodeBlock, String>() {\n                        @Override\n                        public String apply(CodeBlock input) {\n                          return input.toString();\n                        }\n                      })\n                  .toSet());\n        }\n      }\n    }\n\n    if (requestOptionMethod.annotations.contains(\n        AnnotationSpec.builder(SafeVarargs.class).build())) {\n      suppressions.add(\"unchecked\");\n      suppressions.add(\"varargs\");\n    }\n\n    if (suppressions.isEmpty()) {\n      return null;\n    }\n    // Enforce ordering across compilers (Internal and External compilers end up disagreeing on the\n    // order produced by the Set additions above.)\n    ArrayList<String> suppressionsList = new ArrayList<>(suppressions);\n    Collections.sort(suppressionsList);\n\n    AnnotationSpec.Builder builder = AnnotationSpec.builder(SuppressWarnings.class);\n    for (String suppression : suppressionsList) {\n      builder.addMember(\"value\", \"$S\", suppression);\n    }\n\n    return builder.build();\n  }\n\n  /**\n   * Generates overrides of all methods in {@code com.bumptech.glide.RequestBuilder} that return\n   * {@code com.bumptech.glide.RequestBuilder} so that they return our generated subclass instead.\n   */\n  private List<MethodSpec> generateRequestBuilderOverrides() {\n    TypeMirror rawRequestBuilderType =\n        processingEnv.getTypeUtils().erasure(requestBuilderType.asType());\n    return Lists.transform(\n        processorUtil.findInstanceMethodsReturning(requestBuilderType, rawRequestBuilderType),\n        new Function<ExecutableElement, MethodSpec>() {\n          @Override\n          public MethodSpec apply(ExecutableElement input) {\n            return generateRequestBuilderOverride(input);\n          }\n        });\n  }\n\n  /**\n   * Generates an override of a particular method in {@code com.bumptech.glide.RequestBuilder} that\n   * returns {@code com.bumptech.glide.RequestBuilder} so that it returns our generated subclass\n   * instead.\n   */\n  private MethodSpec generateRequestBuilderOverride(ExecutableElement methodToOverride) {\n    // We've already verified that this method returns a RequestBuilder and RequestBuilders have\n    // exactly one type argument, so this is safe unless those assumptions change.\n    TypeMirror typeArgument =\n        ((DeclaredType) methodToOverride.getReturnType()).getTypeArguments().get(0);\n\n    ParameterizedTypeName generatedRequestBuilderOfType =\n        ParameterizedTypeName.get(generatedRequestBuilderClassName, ClassName.get(typeArgument));\n\n    MethodSpec.Builder builder =\n        processorUtil.overriding(methodToOverride).returns(generatedRequestBuilderOfType);\n    builder.addCode(\n        CodeBlock.builder()\n            .add(\n                \"return ($T) super.$N(\",\n                generatedRequestBuilderOfType,\n                methodToOverride.getSimpleName())\n            .add(\n                FluentIterable.from(builder.build().parameters)\n                    .transform(\n                        new Function<ParameterSpec, String>() {\n                          @Override\n                          public String apply(ParameterSpec input) {\n                            return input.name;\n                          }\n                        })\n                    .join(Joiner.on(\", \")))\n            .add(\");\\n\")\n            .build());\n\n    for (AnnotationMirror mirror : methodToOverride.getAnnotationMirrors()) {\n      builder = builder.addAnnotation(AnnotationSpec.get(mirror));\n    }\n\n    if (methodToOverride.isVarArgs()) {\n      builder =\n          builder\n              .addModifiers(Modifier.FINAL)\n              .addAnnotation(SafeVarargs.class)\n              .addAnnotation(\n                  AnnotationSpec.builder(SuppressWarnings.class)\n                      .addMember(\"value\", \"$S\", \"varargs\")\n                      .build());\n    }\n\n    return builder.build();\n  }\n\n  private List<MethodSpec> generateConstructors() {\n    ParameterizedTypeName classOfTranscodeType =\n        ParameterizedTypeName.get(ClassName.get(Class.class), transcodeTypeName);\n\n    TypeName wildcardOfObject = WildcardTypeName.subtypeOf(Object.class);\n    ParameterizedTypeName requestBuilderOfWildcardOfObject =\n        ParameterizedTypeName.get(ClassName.get(requestBuilderType), wildcardOfObject);\n\n    MethodSpec firstConstructor =\n        MethodSpec.constructorBuilder()\n            .addParameter(\n                ParameterSpec.builder(classOfTranscodeType, \"transcodeClass\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addParameter(\n                ParameterSpec.builder(requestBuilderOfWildcardOfObject, \"other\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addStatement(\"super($N, $N)\", \"transcodeClass\", \"other\")\n            .build();\n\n    ClassName context = ClassName.get(\"android.content\", \"Context\");\n    ClassName glide = ClassName.get(\"com.bumptech.glide\", \"Glide\");\n    ClassName requestManager = ClassName.get(\"com.bumptech.glide\", \"RequestManager\");\n    MethodSpec secondConstructor =\n        MethodSpec.constructorBuilder()\n            .addParameter(\n                ParameterSpec.builder(glide, \"glide\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addParameter(\n                ParameterSpec.builder(requestManager, \"requestManager\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addParameter(\n                ParameterSpec.builder(classOfTranscodeType, \"transcodeClass\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addParameter(\n                ParameterSpec.builder(context, \"context\")\n                    .addAnnotation(processorUtil.nonNull())\n                    .build())\n            .addStatement(\n                \"super($N, $N ,$N, $N)\", \"glide\", \"requestManager\", \"transcodeClass\", \"context\")\n            .build();\n    return ImmutableList.of(firstConstructor, secondConstructor);\n  }\n\n  /**\n   * Overrides the protected downloadOnly method in {@code com.bumptech.glide.RequestBuilder} to\n   * return our generated subclass instead.\n   */\n  private MethodSpec generateDownloadOnlyRequestMethod() {\n    ParameterizedTypeName generatedRequestBuilderOfFile =\n        ParameterizedTypeName.get(generatedRequestBuilderClassName, ClassName.get(File.class));\n    return MethodSpec.methodBuilder(\"getDownloadOnlyRequest\")\n        .addAnnotation(Override.class)\n        .addAnnotation(processorUtil.checkResult())\n        .addAnnotation(processorUtil.nonNull())\n        .returns(generatedRequestBuilderOfFile)\n        .addModifiers(Modifier.PROTECTED)\n        .addStatement(\n            \"return new $T<>($T.class, $N).apply($N)\",\n            generatedRequestBuilderClassName,\n            File.class,\n            \"this\",\n            \"DOWNLOAD_ONLY_OPTIONS\")\n        .build();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/RequestManagerFactoryGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.TypeSpec;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.util.Elements;\n\n/**\n * Generates an implementation of {@code\n * com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory} that returns a\n * generated {@code com.bumptech.glide.RequestManager} implementation.\n *\n * <p>Generated {@code com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory}\n * classes look like this:\n *\n * <pre>\n * <code>\n * public class GeneratedRequestManagerFactory\n *     implements RequestManagerRetriever.RequestManagerFactory {\n *   {@literal @Override}\n *   public RequestManager build(Glide glide, Lifecycle lifecycle,\n *       RequestManagerTreeNode treeNode) {\n *     return new GeneratedRequestManager(glide, lifecycle, treeNode);\n *   }\n * }\n * </code>\n * </pre>\n */\nfinal class RequestManagerFactoryGenerator {\n  private static final String GLIDE_QUALIFIED_NAME = \"com.bumptech.glide.Glide\";\n  private static final String LIFECYCLE_QUALIFIED_NAME = \"com.bumptech.glide.manager.Lifecycle\";\n  private static final String REQUEST_MANAGER_TREE_NODE_QUALIFIED_NAME =\n      \"com.bumptech.glide.manager.RequestManagerTreeNode\";\n  private static final String REQUEST_MANAGER_FACTORY_QUALIFIED_NAME =\n      \"com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory\";\n  private static final String REQUEST_MANAGER_QUALIFIED_NAME = \"com.bumptech.glide.RequestManager\";\n  private static final ClassName CONTEXT_CLASS_NAME = ClassName.get(\"android.content\", \"Context\");\n\n  static final String GENERATED_REQUEST_MANAGER_FACTORY_PACKAGE_NAME = \"com.bumptech.glide\";\n  static final String GENERATED_REQUEST_MANAGER_FACTORY_SIMPLE_NAME =\n      \"GeneratedRequestManagerFactory\";\n\n  private final TypeElement glideType;\n  private final TypeElement lifecycleType;\n  private final TypeElement requestManagerTreeNodeType;\n  private final TypeElement requestManagerFactoryInterface;\n  private final ClassName requestManagerClassName;\n  private final ProcessorUtil processorUtil;\n\n  RequestManagerFactoryGenerator(ProcessingEnvironment processingEnv, ProcessorUtil processorUtil) {\n    this.processorUtil = processorUtil;\n    Elements elementUtils = processingEnv.getElementUtils();\n    glideType = elementUtils.getTypeElement(GLIDE_QUALIFIED_NAME);\n    lifecycleType = elementUtils.getTypeElement(LIFECYCLE_QUALIFIED_NAME);\n    requestManagerTreeNodeType =\n        elementUtils.getTypeElement(REQUEST_MANAGER_TREE_NODE_QUALIFIED_NAME);\n\n    requestManagerFactoryInterface =\n        elementUtils.getTypeElement(REQUEST_MANAGER_FACTORY_QUALIFIED_NAME);\n\n    TypeElement requestManagerType = elementUtils.getTypeElement(REQUEST_MANAGER_QUALIFIED_NAME);\n    requestManagerClassName = ClassName.get(requestManagerType);\n  }\n\n  TypeSpec generate(String generatedCodePackageName, TypeSpec generatedRequestManagerSpec) {\n    return TypeSpec.classBuilder(GENERATED_REQUEST_MANAGER_FACTORY_SIMPLE_NAME)\n        .addModifiers(Modifier.FINAL)\n        .addSuperinterface(ClassName.get(requestManagerFactoryInterface))\n        .addJavadoc(\"Generated code, do not modify\\n\")\n        .addMethod(\n            MethodSpec.methodBuilder(\"build\")\n                .addModifiers(Modifier.PUBLIC)\n                .addAnnotation(Override.class)\n                .addAnnotation(processorUtil.nonNull())\n                .returns(requestManagerClassName)\n                .addParameter(\n                    ParameterSpec.builder(ClassName.get(glideType), \"glide\")\n                        .addAnnotation(processorUtil.nonNull())\n                        .build())\n                .addParameter(\n                    ParameterSpec.builder(ClassName.get(lifecycleType), \"lifecycle\")\n                        .addAnnotation(processorUtil.nonNull())\n                        .build())\n                .addParameter(\n                    ParameterSpec.builder(ClassName.get(requestManagerTreeNodeType), \"treeNode\")\n                        .addAnnotation(processorUtil.nonNull())\n                        .build())\n                .addParameter(\n                    ParameterSpec.builder(CONTEXT_CLASS_NAME, \"context\")\n                        .addAnnotation(processorUtil.nonNull())\n                        .build())\n                .addStatement(\n                    \"return new $T(glide, lifecycle, treeNode, context)\",\n                    ClassName.get(generatedCodePackageName, generatedRequestManagerSpec.name))\n                .build())\n        .build();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/RequestManagerGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideType;\nimport com.google.common.base.Function;\nimport com.google.common.base.Predicate;\nimport com.google.common.base.Predicates;\nimport com.google.common.collect.FluentIterable;\nimport com.google.common.collect.Lists;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.MethodSpec.Builder;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.ParameterizedTypeName;\nimport com.squareup.javapoet.TypeSpec;\nimport com.squareup.javapoet.TypeVariableName;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.AnnotationMirror;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.type.DeclaredType;\nimport javax.lang.model.type.TypeKind;\nimport javax.lang.model.type.TypeMirror;\nimport javax.lang.model.util.Elements;\n\n/**\n * Generates an implementation of {@code com.bumptech.glide.RequestManager} that contains generated\n * methods from {@link GlideExtension}s and {@link GlideType}.\n *\n * <p>Generated {@code com.bumptech.glide.RequestManager} implementations look like this:\n *\n * <pre>\n * <code>\n * public final class GeneratedRequestManager extends RequestManager {\n *   GeneratedRequestManager(Glide glide, Lifecycle lifecycle, RequestManagerTreeNode treeNode) {\n *     super(glide, lifecycle, treeNode);\n *   }\n *\n *   public RequestBuilder<GifDrawable> asGif() {\n *     RequestBuilder<GifDrawable> requestBuilder = this.as(GifDrawable.class);\n *     GifOptions.asGif(requestBuilder);\n *     return requestBuilder;\n *   }\n * }\n * </code>\n * </pre>\n */\nfinal class RequestManagerGenerator {\n  private static final String GLIDE_QUALIFIED_NAME = \"com.bumptech.glide.Glide\";\n  private static final String REQUEST_MANAGER_QUALIFIED_NAME = \"com.bumptech.glide.RequestManager\";\n  private static final String LIFECYCLE_QUALIFIED_NAME = \"com.bumptech.glide.manager.Lifecycle\";\n  private static final String REQUEST_MANAGER_TREE_NODE_QUALIFIED_NAME =\n      \"com.bumptech.glide.manager.RequestManagerTreeNode\";\n  private static final ClassName CONTEXT_CLASS_NAME = ClassName.get(\"android.content\", \"Context\");\n\n  private static final String GENERATED_REQUEST_MANAGER_SIMPLE_NAME = \"GlideRequests\";\n\n  private ProcessingEnvironment processingEnv;\n  private final ProcessorUtil processorUtil;\n  private final ClassName requestManagerClassName;\n  private final TypeElement lifecycleType;\n  private final TypeElement requestManagerTreeNodeType;\n  private final TypeElement glideType;\n  private final TypeElement requestManagerType;\n  private final TypeElement requestBuilderType;\n  private ClassName generatedRequestBuilderClassName;\n\n  RequestManagerGenerator(ProcessingEnvironment processingEnv, ProcessorUtil processorUtil) {\n    this.processingEnv = processingEnv;\n    this.processorUtil = processorUtil;\n\n    Elements elementUtils = processingEnv.getElementUtils();\n\n    requestManagerType = elementUtils.getTypeElement(REQUEST_MANAGER_QUALIFIED_NAME);\n    requestManagerClassName = ClassName.get(requestManagerType);\n\n    lifecycleType = elementUtils.getTypeElement(LIFECYCLE_QUALIFIED_NAME);\n    requestManagerTreeNodeType =\n        elementUtils.getTypeElement(REQUEST_MANAGER_TREE_NODE_QUALIFIED_NAME);\n\n    requestBuilderType =\n        elementUtils.getTypeElement(RequestBuilderGenerator.REQUEST_BUILDER_QUALIFIED_NAME);\n\n    glideType = elementUtils.getTypeElement(GLIDE_QUALIFIED_NAME);\n  }\n\n  TypeSpec generate(\n      String generatedCodePackageName,\n      @Nullable TypeSpec requestOptions,\n      TypeSpec requestBuilder,\n      Set<String> glideExtensions) {\n    generatedRequestBuilderClassName = ClassName.get(generatedCodePackageName, requestBuilder.name);\n    return TypeSpec.classBuilder(GENERATED_REQUEST_MANAGER_SIMPLE_NAME)\n        .superclass(requestManagerClassName)\n        .addJavadoc(\n            \"Includes all additions from methods in {@link $T}s\\n\"\n                + \"annotated with {@link $T}\\n\"\n                + \"\\n\"\n                + \"<p>Generated code, do not modify\\n\",\n            GlideExtension.class,\n            GlideType.class)\n        .addAnnotation(\n            AnnotationSpec.builder(SuppressWarnings.class)\n                .addMember(\"value\", \"$S\", \"deprecation\")\n                .build())\n        .addModifiers(Modifier.PUBLIC)\n        .addMethod(generateAsMethod(generatedCodePackageName, requestBuilder))\n        .addMethod(generateCallSuperConstructor())\n        .addMethods(generateExtensionRequestManagerMethods(glideExtensions))\n        .addMethods(generateRequestManagerRequestManagerMethodOverrides(generatedCodePackageName))\n        .addMethods(generateRequestManagerRequestBuilderMethodOverrides())\n        .addMethods(\n            FluentIterable.from(\n                    Collections.singletonList(\n                        generateOverrideSetRequestOptions(\n                            generatedCodePackageName, requestOptions)))\n                .filter(Predicates.<MethodSpec>notNull()))\n        .build();\n  }\n\n  private MethodSpec generateCallSuperConstructor() {\n    return MethodSpec.constructorBuilder()\n        .addModifiers(Modifier.PUBLIC)\n        .addParameter(\n            ParameterSpec.builder(ClassName.get(glideType), \"glide\")\n                .addAnnotation(processorUtil.nonNull())\n                .build())\n        .addParameter(\n            ParameterSpec.builder(ClassName.get(lifecycleType), \"lifecycle\")\n                .addAnnotation(processorUtil.nonNull())\n                .build())\n        .addParameter(\n            ParameterSpec.builder(ClassName.get(requestManagerTreeNodeType), \"treeNode\")\n                .addAnnotation(processorUtil.nonNull())\n                .build())\n        .addParameter(\n            ParameterSpec.builder(CONTEXT_CLASS_NAME, \"context\")\n                .addAnnotation(processorUtil.nonNull())\n                .build())\n        .addStatement(\"super(glide, lifecycle, treeNode, context)\")\n        .build();\n  }\n\n  private MethodSpec generateAsMethod(String generatedCodePackageName, TypeSpec requestBuilder) {\n    TypeVariableName resourceType = TypeVariableName.get(\"ResourceType\");\n    ParameterizedTypeName classOfResouceType =\n        ParameterizedTypeName.get(ClassName.get(Class.class), resourceType);\n\n    ClassName generatedRequestBuilderClassName =\n        ClassName.get(generatedCodePackageName, requestBuilder.name);\n\n    ParameterizedTypeName requestBuilderOfResourceType =\n        ParameterizedTypeName.get(generatedRequestBuilderClassName, resourceType);\n\n    return MethodSpec.methodBuilder(\"as\")\n        .addModifiers(Modifier.PUBLIC)\n        .addAnnotation(Override.class)\n        .addAnnotation(processorUtil.checkResult())\n        .addAnnotation(processorUtil.nonNull())\n        .addTypeVariable(TypeVariableName.get(\"ResourceType\"))\n        .returns(requestBuilderOfResourceType)\n        .addParameter(\n            classOfResouceType.annotated(AnnotationSpec.builder(processorUtil.nonNull()).build()),\n            \"resourceClass\")\n        .addStatement(\n            \"return new $T<>(glide, this, resourceClass, context)\",\n            this.generatedRequestBuilderClassName)\n        .build();\n  }\n\n  /** Generates the list of overrides of methods that return {@code RequestManager}. */\n  private List<MethodSpec> generateRequestManagerRequestManagerMethodOverrides(\n      final String generatedPackageName) {\n    return FluentIterable.from(\n            processorUtil.findInstanceMethodsReturning(requestManagerType, requestManagerType))\n        .transform(\n            new Function<ExecutableElement, MethodSpec>() {\n              @Override\n              public MethodSpec apply(@Nullable ExecutableElement input) {\n                return generateRequestManagerRequestManagerMethodOverride(\n                    generatedPackageName, input);\n              }\n            })\n        .toList();\n  }\n\n  private MethodSpec generateRequestManagerRequestManagerMethodOverride(\n      String generatedPackageName, ExecutableElement method) {\n    ClassName generatedRequestManagerName =\n        ClassName.get(generatedPackageName, GENERATED_REQUEST_MANAGER_SIMPLE_NAME);\n    Builder returns =\n        processorUtil\n            .overriding(method)\n            .addAnnotation(processorUtil.nonNull())\n            .returns(generatedRequestManagerName);\n    return returns\n        .addCode(\n            ProcessorUtil.generateCastingSuperCall(generatedRequestManagerName, returns.build()))\n        .build();\n  }\n\n  /** Generates the list of overrides of methods that return {@code RequestBuilder}. */\n  private List<MethodSpec> generateRequestManagerRequestBuilderMethodOverrides() {\n    // Without the erasure, this is a RequestBuilder<Y>. A RequestBuilder<X> is not assignable to a\n    // RequestBuilder<Y>. After type erasure this is a RequestBuilder. A RequestBuilder<X> is\n    // assignable to the raw RequestBuilder.\n    TypeMirror rawRequestBuilder =\n        processingEnv.getTypeUtils().erasure(requestBuilderType.asType());\n\n    return FluentIterable.from(\n            processorUtil.findInstanceMethodsReturning(requestManagerType, rawRequestBuilder))\n        .filter(\n            new Predicate<ExecutableElement>() {\n              @Override\n              public boolean apply(ExecutableElement input) {\n                // Skip the <T> as(Class<T>) method.\n                return !input.getSimpleName().toString().equals(\"as\");\n              }\n            })\n        .transform(\n            new Function<ExecutableElement, MethodSpec>() {\n              @Override\n              public MethodSpec apply(ExecutableElement input) {\n                return generateRequestManagerRequestBuilderMethodOverride(input);\n              }\n            })\n        .toList();\n  }\n\n  /**\n   * Generates overrides of existing RequestManager methods so that they return our generated\n   * RequestBuilder subtype.\n   */\n  private MethodSpec generateRequestManagerRequestBuilderMethodOverride(\n      ExecutableElement methodToOverride) {\n    // We've already verified that this method returns a RequestBuilder and RequestBuilders have\n    // exactly one type argument, so this is safe unless those assumptions change.\n    TypeMirror typeArgument =\n        ((DeclaredType) methodToOverride.getReturnType()).getTypeArguments().get(0);\n\n    ParameterizedTypeName generatedRequestBuilderOfType =\n        ParameterizedTypeName.get(generatedRequestBuilderClassName, ClassName.get(typeArgument));\n\n    MethodSpec.Builder builder =\n        processorUtil.overriding(methodToOverride).returns(generatedRequestBuilderOfType);\n    builder.addCode(\n        ProcessorUtil.generateCastingSuperCall(generatedRequestBuilderOfType, builder.build()));\n\n    for (AnnotationMirror mirror : methodToOverride.getAnnotationMirrors()) {\n      builder.addAnnotation(AnnotationSpec.get(mirror));\n    }\n    return builder.build();\n  }\n\n  private List<MethodSpec> generateExtensionRequestManagerMethods(Set<String> glideExtensions) {\n    List<ExecutableElement> requestManagerExtensionMethods =\n        processorUtil.findAnnotatedElementsInClasses(glideExtensions, GlideType.class);\n\n    return Lists.transform(\n        requestManagerExtensionMethods,\n        new Function<ExecutableElement, MethodSpec>() {\n          @Override\n          public MethodSpec apply(ExecutableElement input) {\n            return generateAdditionalRequestManagerMethod(input);\n          }\n        });\n  }\n\n  // Generates methods added to RequestManager via GlideExtensions.\n  private MethodSpec generateAdditionalRequestManagerMethod(ExecutableElement extensionMethod) {\n    if (extensionMethod.getReturnType().getKind() == TypeKind.VOID) {\n      return generateAdditionalRequestManagerMethodLegacy(extensionMethod);\n    } else {\n      return generateAdditionalRequestManagerMethodNew(extensionMethod);\n    }\n  }\n\n  private MethodSpec generateAdditionalRequestManagerMethodLegacy(\n      ExecutableElement extensionMethod) {\n    String returnType =\n        processorUtil\n            .findClassValuesFromAnnotationOnClassAsNames(extensionMethod, GlideType.class)\n            .iterator()\n            .next();\n    ClassName returnTypeClassName = ClassName.bestGuess(returnType);\n    ParameterizedTypeName parameterizedTypeName =\n        ParameterizedTypeName.get(generatedRequestBuilderClassName, returnTypeClassName);\n\n    return MethodSpec.methodBuilder(extensionMethod.getSimpleName().toString())\n        .addModifiers(Modifier.PUBLIC)\n        .returns(parameterizedTypeName)\n        .addJavadoc(processorUtil.generateSeeMethodJavadoc(extensionMethod))\n        .addAnnotation(processorUtil.nonNull())\n        .addAnnotation(processorUtil.checkResult())\n        .addStatement(\n            \"$T requestBuilder = this.as($T.class)\", parameterizedTypeName, returnTypeClassName)\n        .addStatement(\n            \"$T.$N(requestBuilder)\",\n            extensionMethod.getEnclosingElement(),\n            extensionMethod.getSimpleName())\n        .addStatement(\"return requestBuilder\")\n        .build();\n  }\n\n  private MethodSpec generateAdditionalRequestManagerMethodNew(ExecutableElement extensionMethod) {\n    String returnType =\n        processorUtil\n            .findClassValuesFromAnnotationOnClassAsNames(extensionMethod, GlideType.class)\n            .iterator()\n            .next();\n    ClassName returnTypeClassName = ClassName.bestGuess(returnType);\n    ParameterizedTypeName parameterizedTypeName =\n        ParameterizedTypeName.get(generatedRequestBuilderClassName, returnTypeClassName);\n\n    return MethodSpec.methodBuilder(extensionMethod.getSimpleName().toString())\n        .addModifiers(Modifier.PUBLIC)\n        .returns(parameterizedTypeName)\n        .addJavadoc(processorUtil.generateSeeMethodJavadoc(extensionMethod))\n        .addAnnotation(processorUtil.nonNull())\n        .addAnnotation(processorUtil.checkResult())\n        .addStatement(\n            \"return ($T) $T.$N(this.as($T.class))\",\n            parameterizedTypeName,\n            extensionMethod.getEnclosingElement(),\n            extensionMethod.getSimpleName(),\n            returnTypeClassName)\n        .build();\n  }\n\n  /**\n   * The {@code RequestOptions} subclass should always be our generated subclass type to avoid\n   * inadvertent errors where a different subclass is applied that accidentally wipes out some logic\n   * in overidden methods in our generated subclass.\n   */\n  @Nullable\n  private MethodSpec generateOverrideSetRequestOptions(\n      String generatedCodePackageName, @Nullable TypeSpec generatedRequestOptions) {\n    if (generatedRequestOptions == null) {\n      return null;\n    }\n\n    Elements elementUtils = processingEnv.getElementUtils();\n    TypeElement requestOptionsType =\n        elementUtils.getTypeElement(RequestOptionsGenerator.REQUEST_OPTIONS_QUALIFIED_NAME);\n\n    // This class may have just been generated and therefore may not be found if we try to obtain\n    // it via Elements, so use just the String version instead.\n    String generatedRequestOptionsQualifiedName =\n        generatedCodePackageName + \".\" + generatedRequestOptions.name;\n\n    String methodName = \"setRequestOptions\";\n    String parameterName = \"toSet\";\n\n    return MethodSpec.methodBuilder(methodName)\n        .addAnnotation(Override.class)\n        .addModifiers(Modifier.PROTECTED)\n        .addParameter(\n            ParameterSpec.builder(ClassName.get(requestOptionsType), parameterName)\n                .addAnnotation(processorUtil.nonNull())\n                .build())\n        .beginControlFlow(\n            \"if ($N instanceof $L)\", parameterName, generatedRequestOptionsQualifiedName)\n        .addStatement(\"super.$N($N)\", methodName, parameterName)\n        .nextControlFlow(\"else\")\n        .addStatement(\n            \"super.setRequestOptions(new $L().apply($N))\",\n            generatedRequestOptionsQualifiedName,\n            parameterName)\n        .endControlFlow()\n        .build();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/RequestOptionsExtensionGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.GlideOption.OVERRIDE_EXTEND;\n\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.CodeBlock;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.TypeName;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.VariableElement;\nimport javax.lang.model.type.TypeKind;\n\n/**\n * Generates method overrides for classes that want to mix in {@link GlideOption} annotated methods\n * in Glide extensions.\n */\nfinal class RequestOptionsExtensionGenerator {\n  private TypeName containingClassName;\n  private ProcessorUtil processorUtil;\n\n  RequestOptionsExtensionGenerator(TypeName containingClassName, ProcessorUtil processorUtil) {\n    this.containingClassName = containingClassName;\n    this.processorUtil = processorUtil;\n  }\n\n  /**\n   * Returns the set of {@link GlideOption} annotated methods in the classes that correspond to the\n   * given extension class names.\n   */\n  List<ExecutableElement> getRequestOptionExtensionMethods(Set<String> glideExtensionClassNames) {\n    return processorUtil.findAnnotatedElementsInClasses(\n        glideExtensionClassNames, GlideOption.class);\n  }\n\n  /**\n   * Returns a list containing an override {@link MethodSpec} for all {@link GlideOption} annotated\n   * methods in the classes that correspond to the given extension class names.\n   */\n  List<MethodSpec> generateInstanceMethodsForExtensions(Set<String> glideExtensionClassNames) {\n    List<ExecutableElement> requestOptionExtensionMethods =\n        getRequestOptionExtensionMethods(glideExtensionClassNames);\n\n    List<MethodSpec> result = new ArrayList<>(requestOptionExtensionMethods.size());\n    for (ExecutableElement requestOptionsExtensionMethod : requestOptionExtensionMethods) {\n      result.add(generateMethodsForRequestOptionsExtension(requestOptionsExtensionMethod));\n    }\n\n    return result;\n  }\n\n  private MethodSpec generateMethodsForRequestOptionsExtension(ExecutableElement element) {\n    // Assert for legacy versions\n    if (element.getReturnType().getKind() == TypeKind.VOID) {\n      throw new IllegalArgumentException(\n          \"The \"\n              + element.getSimpleName()\n              + \" method annotated with @GlideOption in the \"\n              + element.getEnclosingElement().getSimpleName()\n              + \" @GlideExtension is using a legacy\"\n              + \" format that is no longer supported. Please change your method definition so that\"\n              + \" your @GlideModule annotated methods return BaseRequestOptions<?> objects instead\"\n              + \" of null.\");\n    }\n\n    int overrideType = processorUtil.getOverrideType(element);\n\n    String methodName = element.getSimpleName().toString();\n    MethodSpec.Builder builder =\n        MethodSpec.methodBuilder(methodName)\n            .addModifiers(Modifier.PUBLIC)\n            .addJavadoc(processorUtil.generateSeeMethodJavadoc(element))\n            .varargs(element.isVarArgs())\n            .returns(containingClassName)\n            .addAnnotation(\n                AnnotationSpec.builder(SuppressWarnings.class)\n                    .addMember(\"value\", \"$S\", \"unchecked\")\n                    .build());\n\n    // The 0th element is expected to be a RequestOptions object.\n    List<? extends VariableElement> paramElements =\n        element.getParameters().subList(1, element.getParameters().size());\n    List<ParameterSpec> parameters = processorUtil.getParameters(paramElements);\n    builder.addParameters(parameters);\n\n    String extensionRequestOptionsArgument;\n    if (overrideType == OVERRIDE_EXTEND) {\n      builder\n          .addJavadoc(\n              processorUtil.generateSeeMethodJavadoc(\n                  containingClassName, methodName, paramElements))\n          .addAnnotation(Override.class);\n\n      List<Object> methodArgs = new ArrayList<>();\n      methodArgs.add(element.getSimpleName().toString());\n      StringBuilder methodLiterals = new StringBuilder();\n      if (!parameters.isEmpty()) {\n        for (ParameterSpec parameter : parameters) {\n          methodLiterals.append(\"$L, \");\n          methodArgs.add(parameter.name);\n        }\n        methodLiterals =\n            new StringBuilder(methodLiterals.substring(0, methodLiterals.length() - 2));\n      }\n      extensionRequestOptionsArgument =\n          CodeBlock.builder()\n              .add(\"super.$N(\" + methodLiterals + \")\", methodArgs.toArray(new Object[0]))\n              .build()\n              .toString();\n    } else {\n      extensionRequestOptionsArgument = \"this\";\n    }\n\n    List<Object> args = new ArrayList<>();\n    StringBuilder code = new StringBuilder(\"return ($T) $T.$L($L, \");\n    args.add(containingClassName);\n    args.add(ClassName.get(element.getEnclosingElement().asType()));\n    args.add(element.getSimpleName().toString());\n    args.add(extensionRequestOptionsArgument);\n    if (!parameters.isEmpty()) {\n      for (ParameterSpec parameter : parameters) {\n        code.append(\"$L, \");\n        args.add(parameter.name);\n      }\n    }\n    code = new StringBuilder(code.substring(0, code.length() - 2));\n    code.append(\")\");\n    builder.addStatement(code.toString(), args.toArray(new Object[0]));\n\n    builder.addAnnotation(processorUtil.checkResult()).addAnnotation(processorUtil.nonNull());\n\n    return builder.build();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/RequestOptionsGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.google.common.base.Function;\nimport com.google.common.base.Objects;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Predicate;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.FluentIterable;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.Lists;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.CodeBlock;\nimport com.squareup.javapoet.CodeBlock.Builder;\nimport com.squareup.javapoet.FieldSpec;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.TypeName;\nimport com.squareup.javapoet.TypeSpec;\nimport com.squareup.javapoet.TypeVariableName;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.element.TypeParameterElement;\nimport javax.lang.model.element.VariableElement;\n\n/**\n * Generates a new implementation of {@code com.bumptech.glide.request.RequestOptions} containing\n * static versions of methods included in the base class and static and instance versions of all\n * methods annotated with {@link GlideOption} in classes annotated with {@link GlideExtension}.\n *\n * <p>The generated class looks something like this:\n *\n * <pre>\n * <code>\n * public final class GlideOptions extends com.bumptech.glide.request.RequestOptions {\n *\n *   public static com.google.android.apps.photos.glide.GlideOptions signatureOf(\n *       com.bumptech.glide.load.Key arg0) {\n *     return new com.google.android.apps.photos.glide.GlideOptions()\n *         .apply(com.bumptech.glide.request.RequestOptions.signatureOf(arg0));\n *   }\n *\n *   ... // The rest of the static versions of methods from RequestOptions go here.\n *\n *   // Now on to methods generated from an extension:\n *   public com.bumptech.glide.GlideOptions dontAnimate() {\n *     com.bumptech.glide.integration.gifdecoder.GifOptions.dontAnimate(this);\n *     return this;\n *   }\n *\n *   public static com.bumptech.glide.GlideOptions noAnimation() {\n *     return new com.bumptech.glide.GlideOptions().dontAnimate();\n *   }\n * }\n * </code>\n * </pre>\n */\nfinal class RequestOptionsGenerator {\n  private static final String GENERATED_REQUEST_OPTIONS_SIMPLE_NAME = \"GlideOptions\";\n  static final String REQUEST_OPTIONS_PACKAGE_NAME = \"com.bumptech.glide.request\";\n  private static final String REQUEST_OPTIONS_SIMPLE_NAME = \"RequestOptions\";\n  static final String REQUEST_OPTIONS_QUALIFIED_NAME =\n      REQUEST_OPTIONS_PACKAGE_NAME + \".\" + REQUEST_OPTIONS_SIMPLE_NAME;\n\n  static final String BASE_REQUEST_OPTIONS_SIMPLE_NAME = \"BaseRequestOptions\";\n  static final String BASE_REQUEST_OPTIONS_QUALIFIED_NAME =\n      REQUEST_OPTIONS_PACKAGE_NAME + \".\" + BASE_REQUEST_OPTIONS_SIMPLE_NAME;\n\n  private int nextFieldId;\n\n  private final ClassName requestOptionsName;\n  private final TypeElement requestOptionsType;\n  private final ProcessorUtil processorUtil;\n  private final RequestOptionsOverrideGenerator requestOptionsOverrideGenerator;\n\n  private ClassName glideOptionsName;\n\n  RequestOptionsGenerator(\n      ProcessingEnvironment processingEnvironment, ProcessorUtil processorUtil) {\n    this.processorUtil = processorUtil;\n\n    requestOptionsName = ClassName.get(REQUEST_OPTIONS_PACKAGE_NAME, REQUEST_OPTIONS_SIMPLE_NAME);\n\n    requestOptionsType =\n        processingEnvironment.getElementUtils().getTypeElement(REQUEST_OPTIONS_QUALIFIED_NAME);\n\n    requestOptionsOverrideGenerator =\n        new RequestOptionsOverrideGenerator(processingEnvironment, processorUtil);\n  }\n\n  TypeSpec generate(String generatedCodePackageName, Set<String> glideExtensionClassNames) {\n    glideOptionsName =\n        ClassName.get(generatedCodePackageName, GENERATED_REQUEST_OPTIONS_SIMPLE_NAME);\n\n    RequestOptionsExtensionGenerator requestOptionsExtensionGenerator =\n        new RequestOptionsExtensionGenerator(glideOptionsName, processorUtil);\n    List<MethodAndStaticVar> instanceMethodsForExtensions =\n        FluentIterable.from(\n                requestOptionsExtensionGenerator.generateInstanceMethodsForExtensions(\n                    glideExtensionClassNames))\n            .transform(\n                new Function<MethodSpec, MethodAndStaticVar>() {\n                  @Override\n                  public MethodAndStaticVar apply(MethodSpec input) {\n                    return new MethodAndStaticVar(input);\n                  }\n                })\n            .toList();\n\n    List<MethodAndStaticVar> staticMethodsForExtensions =\n        FluentIterable.from(\n                requestOptionsExtensionGenerator.getRequestOptionExtensionMethods(\n                    glideExtensionClassNames))\n            .filter(\n                new Predicate<ExecutableElement>() {\n                  @Override\n                  public boolean apply(ExecutableElement input) {\n                    return !skipStaticMethod(input);\n                  }\n                })\n            .transform(\n                new Function<ExecutableElement, MethodAndStaticVar>() {\n                  @Override\n                  public MethodAndStaticVar apply(ExecutableElement input) {\n                    return generateStaticMethodEquivalentForExtensionMethod(input);\n                  }\n                })\n            .toList();\n\n    List<MethodAndStaticVar> methodsForExtensions = new ArrayList<>();\n    methodsForExtensions.addAll(instanceMethodsForExtensions);\n    methodsForExtensions.addAll(staticMethodsForExtensions);\n\n    Set<MethodSignature> extensionMethodSignatures =\n        ImmutableSet.copyOf(\n            Iterables.transform(\n                methodsForExtensions,\n                new Function<MethodAndStaticVar, MethodSignature>() {\n                  @Override\n                  public MethodSignature apply(MethodAndStaticVar f) {\n                    return new MethodSignature(f.method);\n                  }\n                }));\n\n    List<MethodAndStaticVar> staticOverrides = generateStaticMethodOverridesForRequestOptions();\n    List<MethodSpec> instanceOverrides =\n        requestOptionsOverrideGenerator.generateInstanceMethodOverridesForRequestOptions(\n            glideOptionsName);\n\n    List<MethodAndStaticVar> allMethodsAndStaticVars = new ArrayList<>();\n    for (MethodAndStaticVar item : staticOverrides) {\n      if (extensionMethodSignatures.contains(new MethodSignature(item.method))) {\n        continue;\n      }\n      allMethodsAndStaticVars.add(item);\n    }\n    for (MethodSpec methodSpec : instanceOverrides) {\n      if (extensionMethodSignatures.contains(new MethodSignature(methodSpec))) {\n        continue;\n      }\n      allMethodsAndStaticVars.add(new MethodAndStaticVar(methodSpec));\n    }\n    allMethodsAndStaticVars.addAll(methodsForExtensions);\n\n    TypeSpec.Builder classBuilder =\n        TypeSpec.classBuilder(GENERATED_REQUEST_OPTIONS_SIMPLE_NAME)\n            .addAnnotation(\n                AnnotationSpec.builder(SuppressWarnings.class)\n                    .addMember(\"value\", \"$S\", \"deprecation\")\n                    .build())\n            .addJavadoc(generateClassJavadoc(glideExtensionClassNames))\n            .addModifiers(Modifier.FINAL)\n            .addModifiers(Modifier.PUBLIC)\n            .addSuperinterface(Cloneable.class)\n            .superclass(requestOptionsName);\n\n    for (MethodAndStaticVar methodAndStaticVar : allMethodsAndStaticVars) {\n      if (methodAndStaticVar.method != null) {\n        classBuilder.addMethod(methodAndStaticVar.method);\n      }\n      if (methodAndStaticVar.staticField != null) {\n        classBuilder.addField(methodAndStaticVar.staticField);\n      }\n    }\n    return classBuilder.build();\n  }\n\n  private CodeBlock generateClassJavadoc(Set<String> glideExtensionClassNames) {\n    Builder builder =\n        CodeBlock.builder()\n            .add(\n                \"Automatically generated from {@link $T} annotated classes.\\n\",\n                GlideExtension.class)\n            .add(\"\\n\")\n            .add(\"@see $T\\n\", requestOptionsName);\n\n    for (String glideExtensionClass : glideExtensionClassNames) {\n      builder.add(\"@see $T\\n\", ClassName.bestGuess(glideExtensionClass));\n    }\n    return builder.build();\n  }\n\n  private List<MethodAndStaticVar> generateStaticMethodOverridesForRequestOptions() {\n    List<ExecutableElement> staticMethodsThatReturnRequestOptions =\n        processorUtil.findStaticMethodsReturning(requestOptionsType, requestOptionsType);\n    List<MethodAndStaticVar> staticMethods = new ArrayList<>();\n    for (ExecutableElement element : staticMethodsThatReturnRequestOptions) {\n      if (element.getAnnotation(Deprecated.class) != null) {\n        continue;\n      }\n      staticMethods.add(generateStaticMethodEquivalentForRequestOptionsStaticMethod(element));\n    }\n    return staticMethods;\n  }\n\n  /**\n   * This method is a bit of a hack, but it lets us tie the static version of a method with the\n   * instance version. In turn that lets us call the instance versions on the generated subclass,\n   * instead of just delegating to the RequestOptions static methods. Using the instance methods on\n   * the generated subclass allows our static methods to properly call code that overrides an\n   * existing method in RequestOptions.\n   *\n   * <p>The string names here just map between the static methods in {@code\n   * com.bumptech.glide.request.RequestOptions} and the instance methods they call.\n   */\n  private static String getInstanceMethodNameFromStaticMethodName(String staticMethodName) {\n    String equivalentInstanceMethodName;\n    if (\"bitmapTransform\".equals(staticMethodName)) {\n      equivalentInstanceMethodName = \"transform\";\n    } else if (\"decodeTypeOf\".equals(staticMethodName)) {\n      equivalentInstanceMethodName = \"decode\";\n    } else if (staticMethodName.endsWith(\"Transform\")) {\n      equivalentInstanceMethodName = staticMethodName.substring(0, staticMethodName.length() - 9);\n    } else if (staticMethodName.endsWith(\"Of\")) {\n      equivalentInstanceMethodName = staticMethodName.substring(0, staticMethodName.length() - 2);\n    } else if (\"noTransformation\".equals(staticMethodName)) {\n      equivalentInstanceMethodName = \"dontTransform\";\n    } else if (\"noAnimation\".equals(staticMethodName)) {\n      equivalentInstanceMethodName = \"dontAnimate\";\n    } else if (staticMethodName.equals(\"option\")) {\n      equivalentInstanceMethodName = \"set\";\n    } else {\n      throw new IllegalArgumentException(\"Unrecognized static method name: \" + staticMethodName);\n    }\n    return equivalentInstanceMethodName;\n  }\n\n  private MethodAndStaticVar generateStaticMethodEquivalentForRequestOptionsStaticMethod(\n      ExecutableElement staticMethod) {\n    boolean memoize = memoizeStaticMethodFromArguments(staticMethod);\n    String staticMethodName = staticMethod.getSimpleName().toString();\n\n    String equivalentInstanceMethodName =\n        getInstanceMethodNameFromStaticMethodName(staticMethodName);\n\n    MethodSpec.Builder methodSpecBuilder =\n        MethodSpec.methodBuilder(staticMethodName)\n            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n            .addJavadoc(processorUtil.generateSeeMethodJavadoc(staticMethod))\n            .returns(glideOptionsName);\n\n    StringBuilder createNewOptionAndCall =\n        createNewOptionAndCall(\n            memoize, methodSpecBuilder, \"new $T().$N(\", processorUtil.getParameters(staticMethod));\n\n    FieldSpec requiredStaticField = null;\n    if (memoize) {\n      // Generates code that looks like:\n      // if (GlideOptions.<methodName> == null) {\n      //   GlideOptions.<methodName> = new GlideOptions().<methodName>().autoClone()\n      // }\n\n      // Mix in an incrementing unique id to handle method overloading.\n      String staticVariableName = staticMethodName + nextFieldId++;\n      requiredStaticField =\n          FieldSpec.builder(glideOptionsName, staticVariableName)\n              .addModifiers(Modifier.PRIVATE, Modifier.STATIC)\n              .build();\n      methodSpecBuilder\n          .beginControlFlow(\"if ($T.$N == null)\", glideOptionsName, staticVariableName)\n          .addStatement(\n              \"$T.$N =\\n\" + createNewOptionAndCall + \".$N\",\n              glideOptionsName,\n              staticVariableName,\n              glideOptionsName,\n              equivalentInstanceMethodName,\n              \"autoClone()\")\n          .endControlFlow()\n          .addStatement(\"return $T.$N\", glideOptionsName, staticVariableName);\n    } else {\n      // Generates code that looks like:\n      // return new GlideOptions().<methodName>()\n      methodSpecBuilder.addStatement(\n          \"return \" + createNewOptionAndCall, glideOptionsName, equivalentInstanceMethodName);\n    }\n\n    List<? extends TypeParameterElement> typeParameters = staticMethod.getTypeParameters();\n    for (TypeParameterElement typeParameterElement : typeParameters) {\n      methodSpecBuilder.addTypeVariable(\n          TypeVariableName.get(typeParameterElement.getSimpleName().toString()));\n    }\n\n    methodSpecBuilder\n        .addAnnotation(processorUtil.checkResult())\n        .addAnnotation(processorUtil.nonNull());\n\n    return new MethodAndStaticVar(methodSpecBuilder.build(), requiredStaticField);\n  }\n\n  @SuppressWarnings(\"checkstyle:UnnecessaryParentheses\") // Readability\n  private static boolean memoizeStaticMethodFromArguments(ExecutableElement staticMethod) {\n    return staticMethod.getParameters().isEmpty()\n        || (staticMethod.getParameters().size() == 1\n            && staticMethod\n                .getParameters()\n                .get(0)\n                .getSimpleName()\n                .toString()\n                .equals(\"android.content.Context\"));\n  }\n\n  private StringBuilder createNewOptionAndCall(\n      boolean memoize,\n      MethodSpec.Builder methodSpecBuilder,\n      String start,\n      List<ParameterSpec> specs) {\n    StringBuilder createNewOptionAndCall = new StringBuilder(start);\n    if (!specs.isEmpty()) {\n      methodSpecBuilder.addParameters(specs);\n      for (ParameterSpec parameter : specs) {\n        createNewOptionAndCall.append(parameter.name);\n        // use the Application Context to avoid memory leaks.\n        if (memoize && isAndroidContext(parameter)) {\n          createNewOptionAndCall.append(\".getApplicationContext()\");\n        }\n        createNewOptionAndCall.append(\", \");\n      }\n      createNewOptionAndCall =\n          new StringBuilder(\n              createNewOptionAndCall.substring(0, createNewOptionAndCall.length() - 2));\n    }\n    createNewOptionAndCall.append(\")\");\n    return createNewOptionAndCall;\n  }\n\n  private boolean isAndroidContext(ParameterSpec parameter) {\n    return parameter.type.toString().equals(\"android.content.Context\");\n  }\n\n  private MethodAndStaticVar generateStaticMethodEquivalentForExtensionMethod(\n      ExecutableElement instanceMethod) {\n    String staticMethodName = getStaticMethodName(instanceMethod);\n    String instanceMethodName = instanceMethod.getSimpleName().toString();\n    if (Strings.isNullOrEmpty(staticMethodName)) {\n      if (instanceMethodName.startsWith(\"dont\")) {\n        staticMethodName = \"no\" + instanceMethodName.replace(\"dont\", \"\");\n      } else {\n        staticMethodName = instanceMethodName + \"Of\";\n      }\n    }\n    boolean memoize = memoizeStaticMethodFromAnnotation(instanceMethod);\n\n    //noinspection ResultOfMethodCallIgnored\n    Preconditions.checkNotNull(staticMethodName);\n    MethodSpec.Builder methodSpecBuilder =\n        MethodSpec.methodBuilder(staticMethodName)\n            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n            .addJavadoc(processorUtil.generateSeeMethodJavadoc(instanceMethod))\n            .varargs(instanceMethod.isVarArgs())\n            .returns(glideOptionsName);\n\n    List<? extends VariableElement> parameters = instanceMethod.getParameters();\n\n    // Always remove the first parameter because it's always RequestOptions in extensions. The\n    // actual method we want to generate will pass the RequestOptions in to the extension method,\n    // but should not itself require a RequestOptions object to be passed in.\n    if (parameters.isEmpty()) {\n      throw new IllegalArgumentException(\"Expected non-empty parameters for: \" + instanceMethod);\n    }\n    // Remove is not supported.\n    parameters = parameters.subList(1, parameters.size());\n\n    StringBuilder createNewOptionAndCall =\n        createNewOptionAndCall(\n            memoize, methodSpecBuilder, \"new $T().$L(\", processorUtil.getParameters(parameters));\n\n    FieldSpec requiredStaticField = null;\n    if (memoize) {\n      // Generates code that looks like:\n      // if (GlideOptions.<methodName> == null) {\n      //   GlideOptions.<methodName> = new GlideOptions().<methodName>().autoClone()\n      // }\n\n      // Mix in an incrementing unique id to handle method overloading.\n      String staticVariableName = staticMethodName + nextFieldId++;\n      requiredStaticField =\n          FieldSpec.builder(glideOptionsName, staticVariableName)\n              .addModifiers(Modifier.PRIVATE, Modifier.STATIC)\n              .build();\n      methodSpecBuilder\n          .beginControlFlow(\"if ($T.$N == null)\", glideOptionsName, staticVariableName)\n          .addStatement(\n              \"$T.$N =\\n\" + createNewOptionAndCall + \".$N\",\n              glideOptionsName,\n              staticVariableName,\n              glideOptionsName,\n              instanceMethodName,\n              \"autoClone()\")\n          .endControlFlow()\n          .addStatement(\"return $T.$N\", glideOptionsName, staticVariableName);\n    } else {\n      // Generates code that looks like:\n      // return new GlideOptions().<methodName>()\n      methodSpecBuilder.addStatement(\n          \"return \" + createNewOptionAndCall, glideOptionsName, instanceMethodName);\n    }\n\n    List<? extends TypeParameterElement> typeParameters = instanceMethod.getTypeParameters();\n    for (TypeParameterElement typeParameterElement : typeParameters) {\n      methodSpecBuilder.addTypeVariable(\n          TypeVariableName.get(typeParameterElement.getSimpleName().toString()));\n    }\n\n    methodSpecBuilder.addAnnotation(processorUtil.checkResult());\n\n    return new MethodAndStaticVar(methodSpecBuilder.build(), requiredStaticField);\n  }\n\n  @Nullable\n  private static String getStaticMethodName(ExecutableElement element) {\n    GlideOption glideOption = element.getAnnotation(GlideOption.class);\n    String result = glideOption != null ? glideOption.staticMethodName() : null;\n    return Strings.emptyToNull(result);\n  }\n\n  private static boolean memoizeStaticMethodFromAnnotation(ExecutableElement element) {\n    GlideOption glideOption = element.getAnnotation(GlideOption.class);\n    return glideOption != null && glideOption.memoizeStaticMethod();\n  }\n\n  private static boolean skipStaticMethod(ExecutableElement element) {\n    GlideOption glideOption = element.getAnnotation(GlideOption.class);\n    return glideOption != null && glideOption.skipStaticMethod();\n  }\n\n  private static final class MethodAndStaticVar {\n    @Nullable final MethodSpec method;\n    @Nullable final FieldSpec staticField;\n\n    MethodAndStaticVar(@Nullable MethodSpec method) {\n      this(method, null /*staticField*/);\n    }\n\n    MethodAndStaticVar(@Nullable MethodSpec method, @Nullable FieldSpec staticField) {\n      this.method = method;\n      this.staticField = staticField;\n    }\n  }\n\n  private static final class MethodSignature {\n    private final TypeName returnType;\n    private final List<TypeName> parameterTypes;\n    private final boolean isStatic;\n    private final String name;\n\n    MethodSignature(MethodSpec spec) {\n      name = spec.name;\n      isStatic = spec.modifiers.contains(Modifier.STATIC);\n      returnType = spec.returnType;\n      parameterTypes =\n          Lists.transform(\n              spec.parameters,\n              new Function<ParameterSpec, TypeName>() {\n                @Nullable\n                @Override\n                public TypeName apply(ParameterSpec parameterSpec) {\n                  return parameterSpec.type;\n                }\n              });\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof MethodSignature) {\n        MethodSignature other = (MethodSignature) o;\n        return name.equals(other.name)\n            && returnType.equals(other.returnType)\n            && parameterTypes.equals(other.parameterTypes)\n            && isStatic == other.isStatic;\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      return Objects.hashCode(name, returnType, parameterTypes, isStatic);\n    }\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/java/com/bumptech/glide/annotation/compiler/RequestOptionsOverrideGenerator.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.RequestOptionsGenerator.BASE_REQUEST_OPTIONS_QUALIFIED_NAME;\n\nimport com.google.common.base.Function;\nimport com.google.common.base.Joiner;\nimport com.google.common.base.Predicate;\nimport com.google.common.collect.FluentIterable;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.CodeBlock;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterSpec;\nimport com.squareup.javapoet.TypeName;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.lang.model.element.AnnotationMirror;\nimport javax.lang.model.element.ExecutableElement;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\n\n/**\n * Generates overrides for BaseRequestOptions methods so that subclasses' methods return the\n * subclass type, not just BaseRequestOptions.\n */\nfinal class RequestOptionsOverrideGenerator {\n\n  private final TypeElement baseRequestOptionsType;\n  private ProcessorUtil processorUtil;\n\n  RequestOptionsOverrideGenerator(\n      ProcessingEnvironment processingEnv, ProcessorUtil processorUtil) {\n\n    this.processorUtil = processorUtil;\n    baseRequestOptionsType =\n        processingEnv.getElementUtils().getTypeElement(BASE_REQUEST_OPTIONS_QUALIFIED_NAME);\n  }\n\n  List<MethodSpec> generateInstanceMethodOverridesForRequestOptions(TypeName typeToOverrideIn) {\n    return generateInstanceMethodOverridesForRequestOptions(\n        typeToOverrideIn, Collections.<String>emptySet());\n  }\n\n  List<MethodSpec> generateInstanceMethodOverridesForRequestOptions(\n      final TypeName typeToOverrideIn, final Set<String> excludedMethods) {\n    return FluentIterable.from(\n            processorUtil.findInstanceMethodsReturning(\n                baseRequestOptionsType, baseRequestOptionsType))\n        .filter(\n            new Predicate<ExecutableElement>() {\n              @Override\n              public boolean apply(ExecutableElement input) {\n                return !excludedMethods.contains(input.getSimpleName().toString());\n              }\n            })\n        .transform(\n            new Function<ExecutableElement, MethodSpec>() {\n              @Override\n              public MethodSpec apply(ExecutableElement input) {\n                return generateRequestOptionOverride(typeToOverrideIn, input);\n              }\n            })\n        .toList();\n  }\n\n  private MethodSpec generateRequestOptionOverride(\n      TypeName typeToOverrideIn, ExecutableElement methodToOverride) {\n    MethodSpec.Builder result =\n        processorUtil.overriding(methodToOverride).returns(typeToOverrideIn);\n    result.addCode(\n        CodeBlock.builder()\n            .add(\"return ($T) super.$N(\", typeToOverrideIn, methodToOverride.getSimpleName())\n            .add(\n                FluentIterable.from(result.build().parameters)\n                    .transform(\n                        new Function<ParameterSpec, String>() {\n                          @Override\n                          public String apply(ParameterSpec input) {\n                            return input.name;\n                          }\n                        })\n                    .join(Joiner.on(\", \")))\n            .add(\");\\n\")\n            .build());\n\n    if (methodToOverride.getSimpleName().toString().contains(\"transform\")\n        && methodToOverride.isVarArgs()) {\n      result\n          .addModifiers(Modifier.FINAL)\n          .addAnnotation(SafeVarargs.class)\n          .addAnnotation(\n              AnnotationSpec.builder(SuppressWarnings.class)\n                  .addMember(\"value\", \"$S\", \"varargs\")\n                  .build());\n    }\n\n    for (AnnotationMirror mirror : methodToOverride.getAnnotationMirrors()) {\n      result.addAnnotation(AnnotationSpec.get(mirror));\n    }\n\n    return result.build();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors",
    "content": "com.bumptech.glide.annotation.compiler.GlideAnnotationProcessor,aggregating\n"
  },
  {
    "path": "annotation/compiler/test/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "annotation/compiler/test/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    sourceSets {\n        test {\n            resources {\n                // *.java is excluded by default...\n                setExcludes([])\n            }\n            // TODO: Re-enable these tests after fixing import orders.\n            java {\n                exclude \"**/AppGlideModuleWithExcludesTest.java\"\n                exclude \"**/AppGlideModuleWithLibraryInPackageTest.java\"\n                exclude \"**/AppGlideModuleWithMultipleExcludesTest.java\"\n                exclude \"**/EmptyAppAndLibraryGlideModulesTest.java\"\n                exclude \"**/GlideExtensionWithOptionTest.java\"\n                exclude \"**/GlideExtensionWithTypeTest.java\"\n                exclude \"**/GlideExtensionWithTypeTest.java\"\n            }\n        }\n    }\n}\n\nafterEvaluate {\n    lint.enabled = false\n    compileReleaseJavaWithJavac.enabled = false\n}\n\nandroid {\n    namespace 'com.bumptech.glide.annotation.compiler.test'\n    compileSdk libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk libs.versions.min.sdk.version.get() as int\n        targetSdk libs.versions.target.sdk.version.get() as int\n        versionName VERSION_NAME as String\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n    testOptions {\n        unitTests {\n            all { Test testTask ->\n                testTask.maxParallelForks = 2\n            }\n        }\n    }\n}\n\n// This special test only submodule exists because adding the :glide dependency seems to break\n// the annotation processor dependency chain for the internal sample apps. It's also somewhat\n// easier to parse as a separate module given the existing complexity here and in the compiler\n// build.gradle file.\ndependencies {\n    testImplementation project(':glide')\n    testImplementation project(':annotation:compiler')\n    testImplementation libs.junit\n    testImplementation libs.javapoet\n    testImplementation libs.findbugs.jsr305\n    // Using 0.10 of compile-testing is required for Android Studio to function, but not for the\n    // gradle build. Not yet clear why, but it looks like some kind of version conflict between\n    // javapoet, guava and/or truth.\n    //noinspection GradleDependency\n    testImplementation (\"com.google.testing.compile:compile-testing:0.10\") {\n        // We don't use this and including it requires us to list it separatel which would be\n        // confusing.\n        exclude group: \"com.google.auto.value\", module: \"auto-value\"\n    }\n    testImplementation libs.androidx.annotation\n    testImplementation libs.androidx.fragment\n    // TODO: Find some way to include a similar dependency on java 9+ and re-enable these tests in gradle.\n//    testImplementation files(Jvm.current().getJre().homeDir.getAbsolutePath()+'/lib/rt.jar')\n\n    testAnnotationProcessor project(':annotation:compiler')\n    testAnnotationProcessor libs.autoservice\n}\n\ntask regenerateTestResources {\n    group 'Verification'\n    description 'Regenerates all test resource files under annotation/compiler/test/src/test/resources that are compared against the current output to detect regressions'\n    tasks.withType(Test) {\n        systemProperties.put(\"com.bumptech.glide.annotation.compiler.test.regenerate.path\", projectDir)\n    }\n    doFirst {\n        println(\"Regenerating test resources....\")\n    }\n    doLast {\n        println(\"Finished regenerating test resources\")\n    }\n}\n\nafterEvaluate {\n    regenerateTestResources.finalizedBy(testReleaseUnitTest)\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/AppGlideModuleWithExcludesTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.appResource;\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyLibraryModule;\nimport static com.bumptech.glide.annotation.compiler.test.Util.glide;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.ReferencedResource;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests AppGlideModules that use the @Excludes annotation with a single excluded Module class. */\n@RunWith(JUnit4.class)\npublic class AppGlideModuleWithExcludesTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(forResource(\"AppModuleWithExcludes.java\"), emptyLibraryModule());\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideOptionsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideOptions\"))\n        .hasSourceEquivalentTo(appResource(\"GlideOptions.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequest\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequest.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequests\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequests.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilationGeneratesExpectedGlideAppClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideApp\"))\n        .hasSourceEquivalentTo(appResource(\"GlideApp.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGeneratedAppGlideModuleImpl() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedAppGlideModuleImpl\"))\n        .hasSourceEquivalentTo(forResource(\"GeneratedAppGlideModuleImpl.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGeneratedRequestManagerFactory() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedRequestManagerFactory\"))\n        .hasSourceEquivalentTo(appResource(\"GeneratedRequestManagerFactory.java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/AppGlideModuleWithLibraryInPackageTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.appResource;\nimport static com.bumptech.glide.annotation.compiler.test.Util.glide;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.ReferencedResource;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Tests AppGlideModules that use the @Excludes annotation with a single excluded Module class in a\n * strangely named subpackage.\n */\n@RunWith(JUnit4.class)\npublic class AppGlideModuleWithLibraryInPackageTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                forResource(\"AppModuleWithLibraryInPackage.java\"),\n                forResource(\"LibraryModuleInPackage.java\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideOptionsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideOptions\"))\n        .hasSourceEquivalentTo(appResource(\"GlideOptions.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequest\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequest.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequests\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequests.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilationGeneratesExpectedGlideAppClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideApp\"))\n        .hasSourceEquivalentTo(appResource(\"GlideApp.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGeneratedAppGlideModuleImpl() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedAppGlideModuleImpl\"))\n        .hasSourceEquivalentTo(forResource(\"GeneratedAppGlideModuleImpl.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGeneratedRequestManagerFactory() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedRequestManagerFactory\"))\n        .hasSourceEquivalentTo(appResource(\"GeneratedRequestManagerFactory.java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/AppGlideModuleWithMultipleExcludesTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.appResource;\nimport static com.bumptech.glide.annotation.compiler.test.Util.glide;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.ReferencedResource;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Tests AppGlideModules that use the @Excludes annotation with multiple excluded Module classes.\n */\n@RunWith(JUnit4.class)\npublic class AppGlideModuleWithMultipleExcludesTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                forResource(\"AppModuleWithMultipleExcludes.java\"),\n                forResource(\"EmptyLibraryModule1.java\"),\n                forResource(\"EmptyLibraryModule2.java\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideOptionsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideOptions\"))\n        .hasSourceEquivalentTo(appResource(\"GlideOptions.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequest\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequest.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequests\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequests.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilationGeneratesExpectedGlideAppClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideApp\"))\n        .hasSourceEquivalentTo(appResource(\"GlideApp.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGeneratedAppGlideModuleImpl() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedAppGlideModuleImpl\"))\n        .hasSourceEquivalentTo(forResource(\"GeneratedAppGlideModuleImpl.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGeneratedRequestManagerFactory() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedRequestManagerFactory\"))\n        .hasSourceEquivalentTo(appResource(\"GeneratedRequestManagerFactory.java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/EmptyAppAndLibraryGlideModulesTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.annotation;\nimport static com.bumptech.glide.annotation.compiler.test.Util.appResource;\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyAppModule;\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyLibraryModule;\nimport static com.bumptech.glide.annotation.compiler.test.Util.glide;\nimport static com.bumptech.glide.annotation.compiler.test.Util.libraryResource;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.ReferencedResource;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Tests adding both an empty {@link com.bumptech.glide.module.AppGlideModule} and an empty {@link\n * com.bumptech.glide.module.LibraryGlideModule} in a single project.\n */\n@RunWith(JUnit4.class)\npublic class EmptyAppAndLibraryGlideModulesTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(emptyAppModule(), emptyLibraryModule());\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_generatesAllExpectedFiles() {\n    Truth.assertThat(compilation.generatedSourceFiles()).hasSize(7);\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideOptionsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideOptions\"))\n        .hasSourceEquivalentTo(appResource(\"GlideOptions.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequest\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequest.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequests\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequests.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilationGeneratesExpectedGlideAppClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideApp\"))\n        .hasSourceEquivalentTo(appResource(\"GlideApp.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGeneratedAppGlideModuleImpl() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedAppGlideModuleImpl\"))\n        .hasSourceEquivalentTo(forResource(\"GeneratedAppGlideModuleImpl.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGeneratedRequestManagerFactory() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedRequestManagerFactory\"))\n        .hasSourceEquivalentTo(appResource(\"GeneratedRequestManagerFactory.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedIndexer() throws IOException {\n    String expectedClassName =\n        \"GlideIndexer_GlideModule_com_bumptech_glide_test_EmptyLibraryModule\";\n    assertThat(compilation)\n        .generatedSourceFile(annotation(expectedClassName))\n        .hasSourceEquivalentTo(libraryResource(expectedClassName + \".java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/EmptyAppGlideModuleTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.glide;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests adding a single {@link com.bumptech.glide.test.EmptyAppModule} in a project. */\n@RunWith(JUnit4.class)\npublic class EmptyAppGlideModuleTest implements CompilationProvider {\n  private static final String MODULE_NAME = \"EmptyAppModule.java\";\n\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac().withProcessors(new GlideAnnotationProcessor()).compile(forResource(MODULE_NAME));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_generatesAllExpectedFiles() {\n    Truth.assertThat(compilation.generatedSourceFiles()).hasSize(6);\n  }\n\n  @Test\n  public void compilation_generatesExpectedGlideOptionsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideOptions\"))\n        .hasSourceEquivalentTo(forResource(\"GlideOptions.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGlideRequestClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequest\"))\n        .hasSourceEquivalentTo(forResource(\"GlideRequest.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGlideRequestsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequests\"))\n        .hasSourceEquivalentTo(forResource(\"GlideRequests.java\"));\n  }\n\n  @Test\n  public void compilationGeneratesExpectedGlideAppClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideApp\"))\n        .hasSourceEquivalentTo(forResource(\"GlideApp.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGeneratedAppGlideModuleImpl() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedAppGlideModuleImpl\"))\n        .hasSourceEquivalentTo(forResource(\"GeneratedAppGlideModuleImpl.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGeneratedRequestManagerFactory() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedRequestManagerFactory\"))\n        .hasSourceEquivalentTo(forResource(\"GeneratedRequestManagerFactory.java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/EmptyLibraryGlideModuleTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.annotation;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests adding a single {@link com.bumptech.glide.module.LibraryGlideModule} in a project. */\n@RunWith(JUnit4.class)\npublic class EmptyLibraryGlideModuleTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private static final String MODULE_NAME = \"EmptyLibraryModule.java\";\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac().withProcessors(new GlideAnnotationProcessor()).compile(forResource(MODULE_NAME));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_generatesAllExpectedFiles() {\n    Truth.assertThat(compilation.generatedSourceFiles()).hasSize(1);\n  }\n\n  @Test\n  public void compilation_generatesExpectedIndexer() throws IOException {\n    String expectedClassName =\n        \"GlideIndexer_GlideModule_com_bumptech_glide_test_EmptyLibraryModule\";\n    assertThat(compilation)\n        .generatedSourceFile(annotation(expectedClassName))\n        .hasSourceEquivalentTo(forResource(expectedClassName + \".java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/GlideExtensionOptionsTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyAppModule;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.SubDirectory;\nimport com.bumptech.glide.annotation.compiler.test.TestDescription;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Verifies only the output we expect to change based on the various configurations of GlideOptions.\n */\n@RunWith(JUnit4.class)\npublic class GlideExtensionOptionsTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  @Rule public final TestDescription testDescription = new TestDescription();\n  private static final String EXTENSION_NAME = \"Extension.java\";\n  private Compilation currentCompilation;\n\n  @Test\n  @SubDirectory(\"OverrideExtend\")\n  public void compilation_withOverrideExtend_validOptions() throws IOException {\n    runTest(Subject.GlideOptions);\n  }\n\n  @Test\n  @SubDirectory(\"OverrideExtend\")\n  public void compilation_withOverrideExtend_validRequest() throws IOException {\n    runTest(Subject.GlideRequest);\n  }\n\n  @Test\n  @SubDirectory(\"OverrideExtendMultipleArguments\")\n  public void compilation_withOverrideReplace_andMultipleArguments_validOptions()\n      throws IOException {\n    runTest(Subject.GlideOptions);\n  }\n\n  @Test\n  @SubDirectory(\"OverrideExtendMultipleArguments\")\n  public void compilation_withOverrideReplace_andMultipleArguments_validRequest()\n      throws IOException {\n    runTest(Subject.GlideRequest);\n  }\n\n  @Test\n  @SubDirectory(\"OverrideReplace\")\n  public void compilation_withOverrideReplace_validOptions() throws IOException {\n    runTest(Subject.GlideOptions);\n  }\n\n  @Test\n  @SubDirectory(\"OverrideReplace\")\n  public void compilation_withOverrideReplace_validRequest() throws IOException {\n    runTest(Subject.GlideRequest);\n  }\n\n  @Test\n  @SubDirectory(\"StaticMethodName\")\n  public void compilation_withStaticMethodName_validOptions() throws IOException {\n    runTest(Subject.GlideOptions);\n  }\n\n  @Test\n  @SubDirectory(\"StaticMethodName\")\n  public void compilation_withStaticMethodName_validRequest() throws IOException {\n    runTest(Subject.GlideRequest);\n  }\n\n  @Test\n  @SubDirectory(\"MemoizeStaticMethod\")\n  public void compilation_withMemoizeStaticMethod_validOptions() throws IOException {\n    runTest(Subject.GlideOptions);\n  }\n\n  @Test\n  @SubDirectory(\"MemoizeStaticMethod\")\n  public void compilation_withMemoizeStaticMethod_validRequest() throws IOException {\n    runTest(Subject.GlideRequest);\n  }\n\n  @Test\n  @SubDirectory(\"SkipStaticMethod\")\n  public void compilation_withSkipStaticMethod_validOptions() throws IOException {\n    runTest(Subject.GlideOptions);\n  }\n\n  @Test\n  @SubDirectory(\"SkipStaticMethod\")\n  public void compilation_withSkipStaticMethod_validRequest() throws IOException {\n    runTest(Subject.GlideRequest);\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return currentCompilation;\n  }\n\n  private enum Subject {\n    GlideOptions,\n    GlideRequest;\n\n    String file() {\n      return name() + \".java\";\n    }\n  }\n\n  private void runTest(Subject subject) {\n    String subDir = getSubDirectoryName();\n    currentCompilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(emptyAppModule(), extension(subDir));\n    assertThat(currentCompilation).succeededWithoutWarnings();\n\n    assertThat(currentCompilation)\n        .generatedSourceFile(subpackage(subject.name()))\n        .hasSourceEquivalentTo(forResource(subDir, subject.file()));\n  }\n\n  private String getSubDirectoryName() {\n    return testDescription.getDescription().getAnnotation(SubDirectory.class).value();\n  }\n\n  private JavaFileObject extension(String subdir) {\n    return forResource(subdir, EXTENSION_NAME);\n  }\n\n  private JavaFileObject forResource(String subdir, String name) {\n    return Util.forResource(getClass().getSimpleName(), subdir + \"/\" + name);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/GlideExtensionWithOptionTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.appResource;\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyAppModule;\nimport static com.bumptech.glide.annotation.compiler.test.Util.glide;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.ReferencedResource;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Verifies the output of the processor with a simple single extension option in the new option\n * style where extension methods always return values.\n */\n@RunWith(JUnit4.class)\npublic class GlideExtensionWithOptionTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(emptyAppModule(), forResource(\"ExtensionWithOption.java\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_generatesAllExpectedFiles() {\n    Truth.assertThat(compilation.generatedSourceFiles()).hasSize(7);\n  }\n\n  @Test\n  public void compilation_generatesExpectedGlideOptionsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideOptions\"))\n        .hasSourceEquivalentTo(forResource(\"GlideOptions.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGlideRequestClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequest\"))\n        .hasSourceEquivalentTo(forResource(\"GlideRequest.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequests\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequests.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilationGeneratesExpectedGlideAppClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideApp\"))\n        .hasSourceEquivalentTo(appResource(\"GlideApp.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGeneratedAppGlideModuleImpl() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedAppGlideModuleImpl\"))\n        .hasSourceEquivalentTo(appResource(\"GeneratedAppGlideModuleImpl.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGeneratedRequestManagerFactory() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedRequestManagerFactory\"))\n        .hasSourceEquivalentTo(appResource(\"GeneratedRequestManagerFactory.java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/GlideExtensionWithTypeTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.appResource;\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyAppModule;\nimport static com.bumptech.glide.annotation.compiler.test.Util.glide;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.ReferencedResource;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Verifies the output of the processor with a simple single extension type. */\n@RunWith(JUnit4.class)\npublic class GlideExtensionWithTypeTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(emptyAppModule(), forResource(\"ExtensionWithType.java\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_generatesAllExpectedFiles() {\n    Truth.assertThat(compilation.generatedSourceFiles()).hasSize(7);\n  }\n\n  @Test\n  public void compilation_generatesExpectedGlideOptionsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideOptions\"))\n        .hasSourceEquivalentTo(forResource(\"GlideOptions.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGlideRequestClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequest\"))\n        .hasSourceEquivalentTo(appResource(\"GlideRequest.java\"));\n  }\n\n  @Test\n  public void compilation_generatesExpectedGlideRequestsClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideRequests\"))\n        .hasSourceEquivalentTo(forResource(\"GlideRequests.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilationGeneratesExpectedGlideAppClass() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(subpackage(\"GlideApp\"))\n        .hasSourceEquivalentTo(appResource(\"GlideApp.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGeneratedAppGlideModuleImpl() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedAppGlideModuleImpl\"))\n        .hasSourceEquivalentTo(appResource(\"GeneratedAppGlideModuleImpl.java\"));\n  }\n\n  @Test\n  @ReferencedResource\n  public void compilation_generatesExpectedGeneratedRequestManagerFactory() throws IOException {\n    assertThat(compilation)\n        .generatedSourceFile(glide(\"GeneratedRequestManagerFactory\"))\n        .hasSourceEquivalentTo(appResource(\"GeneratedRequestManagerFactory.java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/InvalidAppGlideModuleWithExcludesTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.testing.compile.Compilation;\nimport com.google.testing.compile.JavaFileObjects;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests AppGlideModules with invalid usages of the @Excludes annotation. */\n// Ignore warnings since most methods use assertThrows\n@SuppressWarnings(\"ResultOfMethodCallIgnored\")\n@RunWith(JUnit4.class)\npublic class InvalidAppGlideModuleWithExcludesTest {\n  @Test\n  public void compilation_withMissingExcludedModuleClass_throws() {\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    JavaFileObjects.forSourceLines(\n                        \"AppModuleWithExcludes\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import com.bumptech.glide.annotation.Excludes;\",\n                        \"import com.bumptech.glide.annotation.GlideModule;\",\n                        \"import com.bumptech.glide.module.AppGlideModule;\",\n                        \"import com.bumptech.glide.test.EmptyLibraryModule;\",\n                        \"@GlideModule\",\n                        \"@Excludes(EmptyLibraryModule.class)\",\n                        \"public final class AppModuleWithExcludes extends AppGlideModule {}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withEmptyExcludes_fails() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                JavaFileObjects.forSourceLines(\n                    \"AppModuleWithExcludes\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.Excludes;\",\n                    \"import com.bumptech.glide.annotation.GlideModule;\",\n                    \"import com.bumptech.glide.module.AppGlideModule;\",\n                    \"import com.bumptech.glide.test.EmptyLibraryModule;\",\n                    \"@GlideModule\",\n                    \"@Excludes\",\n                    \"public final class AppModuleWithExcludes extends AppGlideModule {}\"));\n    assertThat(compilation).failed();\n  }\n\n  @Test\n  public void compilation_withNonGlideModule_throws() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                JavaFileObjects.forSourceLines(\n                    \"AppModuleWithExcludes\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.Excludes;\",\n                    \"import com.bumptech.glide.annotation.GlideModule;\",\n                    \"import com.bumptech.glide.module.AppGlideModule;\",\n                    \"import com.bumptech.glide.test.EmptyLibraryModule;\",\n                    \"@GlideModule\",\n                    \"@Excludes(Object.class)\",\n                    \"public final class AppModuleWithExcludes extends AppGlideModule {}\"));\n    assertThat(compilation).failed();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/InvalidGlideExtensionTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyAppModule;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\nimport static org.junit.Assert.fail;\n\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport com.google.testing.compile.JavaFileObjects;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Checks assertions on {@link com.bumptech.glide.annotation.GlideExtension}s themselves. */\n// Avoid warnings when asserting on exceptions.\n@SuppressWarnings(\"ResultOfMethodCallIgnored\")\n@RunWith(JUnit4.class)\npublic class InvalidGlideExtensionTest {\n  @Test\n  public void compilation_withPublicConstructor_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"PublicConstructor\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"@GlideExtension\",\n                  \"public class PublicConstructor { }\"));\n      fail(\"Failed to throw expected exception\");\n    } catch (RuntimeException e) {\n      Throwable cause = e.getCause();\n      Truth.assertThat(cause.getMessage()).contains(\"non-private constructor\");\n      Truth.assertThat(cause.getMessage()).contains(\"PublicConstructor\");\n    }\n  }\n\n  @Test\n  public void compilation_withPackagePrivateExtension_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"PackagePrivateExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"@GlideExtension\",\n                  \"class PackagePrivateExtension {\",\n                  \"  private PackagePrivateExtension() {}\",\n                  \"}\"));\n      fail(\"Failed to throw expected exception\");\n    } catch (RuntimeException e) {\n      Throwable cause = e.getCause();\n      Truth.assertThat(cause.getMessage()).contains(\"must be public\");\n      Truth.assertThat(cause.getMessage()).contains(\"PackagePrivateExtension\");\n    }\n  }\n\n  @Test\n  public void compilation_withConstructorWithParameters_throws() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"ConstructorParametersExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"@GlideExtension\",\n                  \"public class ConstructorParametersExtension {\",\n                  \"  private ConstructorParametersExtension(int failParam) {}\",\n                  \"  public void doSomething() {}\",\n                  \"}\"));\n      fail(\"Failed to get expected exception\");\n    } catch (RuntimeException e) {\n      Throwable cause = e.getCause();\n      Truth.assertThat(cause.getMessage()).contains(\"parameters in the constructor\");\n      Truth.assertThat(cause.getMessage()).contains(\"ConstructorParametersExtension\");\n    }\n  }\n\n  @Test\n  public void compilation_withNonStaticMethod_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  public void doSomething() {}\",\n                    \"}\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_withStaticMethod_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  public static void doSomething() {}\",\n                    \"}\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/InvalidGlideOptionsExtensionTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyAppModule;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.fail;\n\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport com.google.testing.compile.JavaFileObjects;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Checks assertions on {@link com.bumptech.glide.annotation.GlideExtension}s for methods annotated\n * with {@link com.bumptech.glide.annotation.GlideOption}.\n */\n// Ignore warnings since most methods use assertThrows.\n@SuppressWarnings(\"ResultOfMethodCallIgnored\")\n@RunWith(JUnit4.class)\npublic class InvalidGlideOptionsExtensionTest {\n  @Test\n  public void compilation_withAnnotatedNonStaticMethod_fails() {\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    emptyAppModule(),\n                    JavaFileObjects.forSourceLines(\n                        \"Extension\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import com.bumptech.glide.annotation.GlideExtension;\",\n                        \"import com.bumptech.glide.annotation.GlideOption;\",\n                        \"@GlideExtension\",\n                        \"public class Extension {\",\n                        \"  private Extension() {}\",\n                        \"  @GlideOption\",\n                        \"  public void doSomething() {}\",\n                        \"}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_withRequestOptionsArgInWrongOrder_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"NonRequestOptionsFirstArgExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"import com.bumptech.glide.annotation.GlideOption;\",\n                  \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                  \"@GlideExtension\",\n                  \"public class NonRequestOptionsFirstArgExtension{\",\n                  \"  private NonRequestOptionsFirstArgExtension() {}\",\n                  \"  @GlideOption\",\n                  \"  public static BaseRequestOptions<?> doSomething(\",\n                  \"      Object arg1, BaseRequestOptions<?> options) {\",\n                  \"    return options;\",\n                  \"  }\",\n                  \"}\"));\n      fail();\n    } catch (RuntimeException e) {\n      String message = e.getCause().getMessage();\n      Truth.assertThat(message).contains(\"BaseRequestOptions<?> object as their first parameter\");\n      Truth.assertThat(message).contains(\"Object\");\n      Truth.assertThat(message).contains(\"NonRequestOptionsFirstArgExtension\");\n    }\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_withRequestOptionsArg_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideOption;\",\n                    \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @GlideOption\",\n                    \"  public static BaseRequestOptions<?> doSomething(\",\n                    \"      BaseRequestOptions<?> options) {\",\n                    \"    return options;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeeded();\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_withRequestOptionsArgAndOtherArg_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideOption;\",\n                    \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @GlideOption\",\n                    \"  public static BaseRequestOptions<?> doSomething(\",\n                    \"      BaseRequestOptions<?> options, Object arg2) {\",\n                    \"    return options;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeeded();\n  }\n\n  @Test\n  public void compilation_overridingOptionWithoutAnnotationType_fails() {\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    emptyAppModule(),\n                    JavaFileObjects.forSourceLines(\n                        \"Extension\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import com.bumptech.glide.annotation.GlideExtension;\",\n                        \"import com.bumptech.glide.annotation.GlideOption;\",\n                        \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                        \"@GlideExtension\",\n                        \"public class Extension {\",\n                        \"  private Extension() {}\",\n                        \"  @GlideOption\",\n                        \"  public static BaseRequestOptions<?> centerCrop(\",\n                        \"      BaseRequestOptions<?> options) {\",\n                        \"    return options;\",\n                        \"  }\",\n                        \"}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withOverrideExtend_butNotOverridingMethod_fails() {\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    emptyAppModule(),\n                    JavaFileObjects.forSourceLines(\n                        \"Extension\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import com.bumptech.glide.annotation.GlideExtension;\",\n                        \"import com.bumptech.glide.annotation.GlideOption;\",\n                        \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                        \"@GlideExtension\",\n                        \"public class Extension {\",\n                        \"  private Extension() {}\",\n                        \"  @GlideOption(override = GlideOption.OVERRIDE_EXTEND)\",\n                        \"  public static BaseRequestOptions<?> something(\",\n                        \"      BaseRequestOptions<?> options) {\",\n                        \"    return options;\",\n                        \"  }\",\n                        \"}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withOverrideExtend_andOverridingMethod_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideOption;\",\n                    \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @GlideOption(override = GlideOption.OVERRIDE_EXTEND)\",\n                    \"  public static BaseRequestOptions<?> centerCrop(\",\n                    \"      BaseRequestOptions<?> options) {\",\n                    \"    return options;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeeded();\n  }\n\n  @Test\n  public void compilation_withOverrideReplace_butNotOverridingMethod_fails() {\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    emptyAppModule(),\n                    JavaFileObjects.forSourceLines(\n                        \"Extension\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import com.bumptech.glide.annotation.GlideExtension;\",\n                        \"import com.bumptech.glide.annotation.GlideOption;\",\n                        \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                        \"@GlideExtension\",\n                        \"public class Extension {\",\n                        \"  private Extension() {}\",\n                        \"  @GlideOption(override = GlideOption.OVERRIDE_REPLACE)\",\n                        \"  public static BaseRequestOptions<?> something(\",\n                        \"      BaseRequestOptions<?> options) {\",\n                        \"    return options;\",\n                        \"  }\",\n                        \"}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withOverrideReplace_andOverridingMethod_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideOption;\",\n                    \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @GlideOption(override = GlideOption.OVERRIDE_REPLACE)\",\n                    \"  public static BaseRequestOptions<?> centerCrop(\",\n                    \"      BaseRequestOptions<?> options) {\",\n                    \"    return options;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeeded();\n  }\n\n  @Test\n  public void compilation_withRequestOptionsReturnValue_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import androidx.annotation.NonNull;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideOption;\",\n                    \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @NonNull\",\n                    \"  @GlideOption\",\n                    \"  public static BaseRequestOptions<?> doSomething(\",\n                    \"      BaseRequestOptions<?> options) {\",\n                    \"    return options;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_withNonRequestOptionsReturnValue_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"WrongReturnTypeExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import androidx.annotation.NonNull;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"import com.bumptech.glide.annotation.GlideOption;\",\n                  \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                  \"@GlideExtension\",\n                  \"public class WrongReturnTypeExtension {\",\n                  \"  private WrongReturnTypeExtension() {}\",\n                  \"  @NonNull\",\n                  \"  @GlideOption\",\n                  \"  public static Object doSomething(BaseRequestOptions<?> options) {\",\n                  \"    return options;\",\n                  \"  }\",\n                  \"}\"));\n      fail();\n    } catch (RuntimeException e) {\n      String message = e.getCause().getMessage();\n      Truth.assertThat(message)\n          .contains(\"@GlideOption methods should return a BaseRequestOptions<?> object\");\n      Truth.assertThat(message).contains(\"Object\");\n      Truth.assertThat(message).contains(\"WrongReturnTypeExtension\");\n    }\n  }\n\n  @Test\n  public void compilation_withMissingNonNullAnnotation_warns() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideOption;\",\n                    \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @GlideOption\",\n                    \"  public static BaseRequestOptions<?> doSomething(\",\n                    \"      BaseRequestOptions<?> options) {\",\n                    \"    return options;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeeded();\n    assertThat(compilation).hadWarningCount(1);\n    assertThat(compilation).hadWarningContaining(\"androidx.annotation.NonNull\");\n    assertThat(compilation).hadWarningContaining(\"com.bumptech.glide.test.Extension#doSomething\");\n  }\n\n  @Test\n  public void compilation_withNoOptionParameters_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"MissingRequestOptionsExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import androidx.annotation.NonNull;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"import com.bumptech.glide.annotation.GlideOption;\",\n                  \"import com.bumptech.glide.request.BaseRequestOptions;\",\n                  \"@GlideExtension\",\n                  \"public class MissingRequestOptionsExtension {\",\n                  \"  private MissingRequestOptionsExtension() {}\",\n                  \"  @NonNull\",\n                  \"  @GlideOption\",\n                  \"  public static BaseRequestOptions<?> doSomething() {\",\n                  \"    return options;\",\n                  \"  }\",\n                  \"}\"));\n      fail();\n    } catch (RuntimeException e) {\n      String message = e.getCause().getMessage();\n      Truth.assertThat(message).contains(\"BaseRequestOptions<?> object as their first parameter\");\n      Truth.assertThat(message).contains(\"doSomething\");\n      Truth.assertThat(message).contains(\"MissingRequestOptionsExtension\");\n    }\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/InvalidGlideTypeExtensionTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.emptyAppModule;\nimport static com.bumptech.glide.annotation.compiler.test.Util.subpackage;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.fail;\n\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport com.google.testing.compile.JavaFileObjects;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Checks assertions on {@link com.bumptech.glide.annotation.GlideExtension}s for methods annotated\n * with {@link com.bumptech.glide.annotation.GlideType}.\n */\n// Ignore warnings since most methods use assertThrows.\n@SuppressWarnings(\"ResultOfMethodCallIgnored\")\n@RunWith(JUnit4.class)\npublic class InvalidGlideTypeExtensionTest {\n  @Test\n  public void compilation_withAnnotatedNonStaticMethod_fails() {\n    assertThrows(\n        \"@GlideType methods must be static\",\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    emptyAppModule(),\n                    JavaFileObjects.forSourceLines(\n                        \"Extension\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import androidx.annotation.NonNull;\",\n                        \"import com.bumptech.glide.annotation.GlideExtension;\",\n                        \"import com.bumptech.glide.annotation.GlideType;\",\n                        \"@GlideExtension\",\n                        \"public class Extension {\",\n                        \"  private Extension() {}\",\n                        \"  @NonNull\",\n                        \"  @GlideType(Number.class)\",\n                        \"  public RequestBuilder<Number> doSomething(\",\n                        \"      RequestBuilder<Number> builder) {\",\n                        \"    return builder;\",\n                        \"  }\",\n                        \"}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_withoutRequestBuilderArg_fails() {\n    assertThrows(\n        \"@GlideType methods must take a RequestBuilder object as their first and only\"\n            + \" parameter, but given multiple for:\"\n            + \" com.bumptech.glide.test.Extension#doSomething()\",\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    emptyAppModule(),\n                    JavaFileObjects.forSourceLines(\n                        \"Extension\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import com.bumptech.glide.annotation.GlideExtension;\",\n                        \"import com.bumptech.glide.annotation.GlideType;\",\n                        \"@GlideExtension\",\n                        \"public class Extension {\",\n                        \"  private Extension() {}\",\n                        \"  @GlideType(Number.class)\",\n                        \"  public static RequestBuilder<Number> doSomething() {\",\n                        \"    return null;\",\n                        \"  }\",\n                        \"}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_withRequestBuilderArg_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import androidx.annotation.NonNull;\",\n                    \"import com.bumptech.glide.RequestBuilder;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideType;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @NonNull\",\n                    \"  @GlideType(Number.class)\",\n                    \"  public static RequestBuilder<Number> type(RequestBuilder<Number> builder) {\",\n                    \"    return builder;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_withNonRequestBuilderArg_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"WrongParameterTypeExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import androidx.annotation.NonNull;\",\n                  \"import com.bumptech.glide.RequestBuilder;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"import com.bumptech.glide.annotation.GlideType;\",\n                  \"@GlideExtension\",\n                  \"public class WrongParameterTypeExtension {\",\n                  \"  private WrongParameterTypeExtension() {}\",\n                  \"  @NonNull\",\n                  \"  @GlideType(Number.class)\",\n                  \"  public static RequestBuilder<Number> type(Object arg) {\",\n                  \"    return null;\",\n                  \"  }\",\n                  \"}\"));\n    } catch (RuntimeException e) {\n      String message = e.getCause().getMessage();\n      Truth.assertThat(message).contains(\"RequestBuilder object as their first and only parameter\");\n      Truth.assertThat(message).contains(\"Object\");\n      Truth.assertThat(message).contains(\"WrongParameterTypeExtension\");\n    }\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_withRequestBuilderArgAndOtherArg_fails() {\n    assertThrows(\n        \"@GlideType methods must take a RequestBuilder object as their first and only\"\n            + \" parameter, but given multiple for:\"\n            + \" com.bumptech.glide.test.Extension#type(\"\n            + \"com.bumptech.glide.RequestBuilder<java.lang.Number>,\"\n            + \"java.lang.Object)\",\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    emptyAppModule(),\n                    JavaFileObjects.forSourceLines(\n                        \"Extension\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import androidx.annotation.NonNull;\",\n                        \"import com.bumptech.glide.RequestBuilder;\",\n                        \"import com.bumptech.glide.annotation.GlideExtension;\",\n                        \"import com.bumptech.glide.annotation.GlideType;\",\n                        \"@GlideExtension\",\n                        \"public class Extension {\",\n                        \"  private Extension() {}\",\n                        \"  @NonNull\",\n                        \"  @GlideType(Number.class)\",\n                        \"  public static RequestBuilder<Number> type(\",\n                        \"      RequestBuilder<Number> builder, Object arg2) {\",\n                        \"    return builder;\",\n                        \"  }\",\n                        \"}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_overridingExistingType_fails() {\n    final Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import android.graphics.drawable.Drawable;\",\n                    \"import androidx.annotation.NonNull;\",\n                    \"import com.bumptech.glide.RequestBuilder;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideType;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @NonNull\",\n                    \"  @GlideType(Drawable.class)\",\n                    \"  public static RequestBuilder<Drawable> asDrawable(\",\n                    \"      RequestBuilder<Drawable> builder) {\",\n                    \"    return builder;\",\n                    \"  }\",\n                    \"}\"));\n\n    assertThrows(\n        \"error: method asDrawable() is already defined in class\"\n            + \" com.bumptech.glide.test.GlideRequests\",\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            compilation.generatedSourceFile(subpackage(\"GlideRequests\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_returningRequestBuilder_succeeds() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import androidx.annotation.NonNull;\",\n                    \"import com.bumptech.glide.RequestBuilder;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideType;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @NonNull\",\n                    \"  @GlideType(Number.class)\",\n                    \"  public static RequestBuilder<Number> asNumber(\",\n                    \"      RequestBuilder<Number> builder) {\",\n                    \"    return builder;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_returningNonRequestBuilder_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"WrongReturnTypeExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import androidx.annotation.NonNull;\",\n                  \"import com.bumptech.glide.RequestBuilder;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"import com.bumptech.glide.annotation.GlideType;\",\n                  \"@GlideExtension\",\n                  \"public class WrongReturnTypeExtension {\",\n                  \"  private WrongReturnTypeExtension() {}\",\n                  \"  @NonNull\",\n                  \"  @GlideType(Number.class)\",\n                  \"  public static Object asNumber(\",\n                  \"      RequestBuilder<Number> builder) {\",\n                  \"    return new Object();\",\n                  \"  }\",\n                  \"}\"));\n      fail();\n    } catch (RuntimeException e) {\n      String message = e.getCause().getMessage();\n      Truth.assertThat(message).contains(\"@GlideType methods should return a RequestBuilder\");\n      Truth.assertThat(message).contains(\"Number\");\n      Truth.assertThat(message).contains(\"WrongReturnTypeExtension\");\n    }\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_returningBuilderWithIncorrectType_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"WrongBuilderTypeExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import androidx.annotation.NonNull;\",\n                  \"import com.bumptech.glide.RequestBuilder;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"import com.bumptech.glide.annotation.GlideType;\",\n                  \"@GlideExtension\",\n                  \"public class WrongBuilderTypeExtension {\",\n                  \"  private WrongBuilderTypeExtension() {}\",\n                  \"  @NonNull\",\n                  \"  @GlideType(Number.class)\",\n                  \"  public static RequestBuilder<Object> asNumber(\",\n                  \"      RequestBuilder<Object> builder) {\",\n                  \"    return builder;\",\n                  \"  }\",\n                  \"}\"));\n      fail();\n    } catch (RuntimeException e) {\n      String message = e.getCause().getMessage();\n      Truth.assertThat(message)\n          .contains(\"@GlideType methods should return a RequestBuilder<java.lang.Number>\");\n      Truth.assertThat(message).contains(\"WrongBuilderTypeExtension\");\n    }\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_returningBuilder_andMultipleParams_fails() {\n    assertThrows(\n        \"@GlideType methods must take a RequestBuilder object as their first and only parameter,\"\n            + \" but given multiple for:\"\n            + \" com.bumptech.glide.test.Extension#asNumber(\"\n            + \"com.bumptech.glide.RequestBuilder<java.lang.Number>,java.lang.Object)\",\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(\n                    emptyAppModule(),\n                    JavaFileObjects.forSourceLines(\n                        \"Extension\",\n                        \"package com.bumptech.glide.test;\",\n                        \"import androidx.annotation.NonNull;\",\n                        \"import com.bumptech.glide.RequestBuilder;\",\n                        \"import com.bumptech.glide.annotation.GlideExtension;\",\n                        \"import com.bumptech.glide.annotation.GlideType;\",\n                        \"@GlideExtension\",\n                        \"public class Extension {\",\n                        \"  private Extension() {}\",\n                        \"  @NonNull\",\n                        \"  @GlideType(Number.class)\",\n                        \"  public static RequestBuilder<Number> asNumber(\",\n                        \"      RequestBuilder<Number> builder, Object arg1) {\",\n                        \"    return builder;\",\n                        \"  }\",\n                        \"}\"));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_returningBuilder_nonBuilderParam_fails() {\n    try {\n      javac()\n          .withProcessors(new GlideAnnotationProcessor())\n          .compile(\n              emptyAppModule(),\n              JavaFileObjects.forSourceLines(\n                  \"IncorrectParameterExtension\",\n                  \"package com.bumptech.glide.test;\",\n                  \"import androidx.annotation.NonNull;\",\n                  \"import com.bumptech.glide.RequestBuilder;\",\n                  \"import com.bumptech.glide.annotation.GlideExtension;\",\n                  \"import com.bumptech.glide.annotation.GlideType;\",\n                  \"@GlideExtension\",\n                  \"public class IncorrectParameterExtension {\",\n                  \"  private IncorrectParameterExtension() {}\",\n                  \"  @NonNull\",\n                  \"  @GlideType(Number.class)\",\n                  \"  public static RequestBuilder<Number> asNumber(\",\n                  \"      Object arg) {\",\n                  \"    return null;\",\n                  \"  }\",\n                  \"}\"));\n      fail();\n    } catch (RuntimeException e) {\n      String message = e.getCause().getMessage();\n      Truth.assertThat(message)\n          .contains(\n              \"@GlideType methods must take a RequestBuilder object\"\n                  + \" as their first and only parameter\");\n      Truth.assertThat(message).contains(\"Object\");\n      Truth.assertThat(message).contains(\"IncorrectParameterExtension\");\n    }\n  }\n\n  @Test\n  public void compilation_withAnnotatedStaticMethod_returningRequestBuilder_missingNonNull_warns() {\n    Compilation compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                emptyAppModule(),\n                JavaFileObjects.forSourceLines(\n                    \"Extension\",\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.RequestBuilder;\",\n                    \"import com.bumptech.glide.annotation.GlideExtension;\",\n                    \"import com.bumptech.glide.annotation.GlideType;\",\n                    \"@GlideExtension\",\n                    \"public class Extension {\",\n                    \"  private Extension() {}\",\n                    \"  @GlideType(Number.class)\",\n                    \"  public static RequestBuilder<Number> asNumber(\",\n                    \"      RequestBuilder<Number> builder) {\",\n                    \"    return builder;\",\n                    \"  }\",\n                    \"}\"));\n    assertThat(compilation).succeeded();\n    assertThat(compilation).hadWarningCount(1);\n    assertThat(compilation).hadWarningContaining(\"androidx.annotation.NonNull\");\n    assertThat(compilation).hadWarningContaining(\"com.bumptech.glide.test.Extension#asNumber\");\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/MultipleAppGlideModuleTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\nimport static org.junit.Assert.assertThrows;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.testing.compile.Compilation;\nimport javax.tools.JavaFileObject;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Ensures that adding more than one {@link com.bumptech.glide.module.AppGlideModule} to a project\n * will fail.\n */\n@RunWith(JUnit4.class)\npublic class MultipleAppGlideModuleTest implements CompilationProvider {\n  private static final String FIRST_MODULE = \"EmptyAppModule1.java\";\n  private static final String SECOND_MODULE = \"EmptyAppModule2.java\";\n\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  // Throws.\n  @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n  @Test\n  public void compilation_withTwoAppModules_fails() {\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            javac()\n                .withProcessors(new GlideAnnotationProcessor())\n                .compile(forResource(FIRST_MODULE), forResource(SECOND_MODULE));\n          }\n        });\n  }\n\n  @Test\n  public void compilation_withFirstModuleOnly_succeeds() {\n    compilation =\n        javac().withProcessors(new GlideAnnotationProcessor()).compile(forResource(FIRST_MODULE));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_withSecondModuleOnly_succeeds() {\n    compilation =\n        javac().withProcessors(new GlideAnnotationProcessor()).compile(forResource(SECOND_MODULE));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/MultipleEmptyLibraryGlideModuleTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.annotation;\nimport static com.google.testing.compile.CompilationSubject.assertThat;\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.bumptech.glide.annotation.compiler.test.RegenerateResourcesRule;\nimport com.bumptech.glide.annotation.compiler.test.Util;\nimport com.google.common.truth.Truth;\nimport com.google.testing.compile.Compilation;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests adding multiple {@link com.bumptech.glide.module.LibraryGlideModule}s in a project. */\n@RunWith(JUnit4.class)\npublic class MultipleEmptyLibraryGlideModuleTest implements CompilationProvider {\n  @Rule\n  public final RegenerateResourcesRule regenerateResourcesRule = new RegenerateResourcesRule(this);\n\n  private Compilation compilation;\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                forResource(\"EmptyLibraryModule1.java\"), forResource(\"EmptyLibraryModule2.java\"));\n    assertThat(compilation).succeededWithoutWarnings();\n  }\n\n  @Test\n  public void compilation_generatesAllExpectedFiles() {\n    Truth.assertThat(compilation.generatedSourceFiles()).hasSize(1);\n  }\n\n  @Test\n  public void compilation_generatesExpectedIndexerForModules() throws IOException {\n    String expectedClassName =\n        \"GlideIndexer_GlideModule_com_bumptech_glide_test_EmptyLibraryModule1_com_bumptech_glide\"\n            + \"_test_EmptyLibraryModule2\";\n    assertThat(compilation)\n        .generatedSourceFile(annotation(expectedClassName))\n        .hasSourceEquivalentTo(forResource(expectedClassName + \".java\"));\n  }\n\n  private JavaFileObject forResource(String name) {\n    return Util.forResource(getClass().getSimpleName(), name);\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/OverlyLongFileNameTest.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport static com.google.testing.compile.Compiler.javac;\n\nimport com.bumptech.glide.annotation.compiler.test.CompilationProvider;\nimport com.google.testing.compile.Compilation;\nimport com.google.testing.compile.CompilationSubject;\nimport com.google.testing.compile.JavaFileObjects;\nimport java.io.File;\nimport java.io.IOException;\nimport javax.tools.JavaFileObject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Makes sure that we can handle indexers based on long package or file names, or many modules.\n *\n * <p>See #4106.\n */\n@RunWith(JUnit4.class)\npublic class OverlyLongFileNameTest implements CompilationProvider {\n  @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();\n  private Compilation compilation;\n  private static final String FILE_NAME_LONGER_THAN_255_CHARS =\n      \"SomeReallyReallyRidiculouslyLongFileNameOrPackageNameIGuessThatExceedsTwoHundredAndFiftyFive\"\n          + \"CharactersThoughThatsOnlyAroundOneHundredCharactersWhichMeansINeedToKeepTypingToGetTo\"\n          + \"TwoHundredAndFiftyFiveSomehowThankfullyOnlyLikeFiftyToGoNowMaybeButNotQuiteYet\"\n          + \"SomewhereAroundNowIsProbablyGood\";\n\n  @Before\n  public void setUp() {\n    compilation =\n        javac()\n            .withProcessors(new GlideAnnotationProcessor())\n            .compile(\n                JavaFileObjects.forSourceLines(\n                    FILE_NAME_LONGER_THAN_255_CHARS,\n                    \"package com.bumptech.glide.test;\",\n                    \"import com.bumptech.glide.annotation.GlideModule;\",\n                    \"import com.bumptech.glide.module.LibraryGlideModule;\",\n                    \"@GlideModule\",\n                    \"public final class \"\n                        + FILE_NAME_LONGER_THAN_255_CHARS\n                        + \" extends LibraryGlideModule {}\"));\n  }\n\n  @Test\n  public void compilingLongClassAndOrPackageNameShouldSucceed() throws IOException {\n    CompilationSubject.assertThat(compilation).succeededWithoutWarnings();\n    for (JavaFileObject file : compilation.generatedFiles()) {\n      temporaryFolder.create();\n      String actualFileName = new File(file.getName()).getName();\n      if (!actualFileName.startsWith(FILE_NAME_LONGER_THAN_255_CHARS)) {\n        try {\n          temporaryFolder.newFile(actualFileName).createNewFile();\n        } catch (IOException e) {\n          throw new RuntimeException(\"Failed to create: \" + actualFileName, e);\n        }\n      }\n    }\n  }\n\n  @Override\n  public Compilation getCompilation() {\n    return compilation;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/test/CompilationProvider.java",
    "content": "package com.bumptech.glide.annotation.compiler.test;\n\nimport com.google.testing.compile.Compilation;\n\n/** Provides the {@link Compilation} used to compile test code. */\npublic interface CompilationProvider {\n  Compilation getCompilation();\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/test/ReferencedResource.java",
    "content": "package com.bumptech.glide.annotation.compiler.test;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Indicates that the method in question is referencing a test resource that it doesn't \"own\" and\n * should not attempt to regenerate.\n *\n * <p>Used by {@link RegenerateResourcesRule} to ensure that if we are regenerating resources, we're\n * only regenerating them for a single class and only for the single class that has the correct name\n * and directory sequence so that we update the correct file.\n *\n * <p>Ideally this wouldn't be necessary. It would be great if we could find a way to go from the\n * test failure more directly to the actual path of the resource used. Right now we're basically\n * guessing based on this annotation, the class name of the test class, and any values from {@link\n * SubDirectory}. Without this annotation, we'd end up writing files that were never used.\n */\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface ReferencedResource {}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/test/RegenerateResourcesRule.java",
    "content": "package com.bumptech.glide.annotation.compiler.test;\n\nimport static com.bumptech.glide.annotation.compiler.test.Util.asUnixChars;\n\nimport androidx.annotation.NonNull;\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.Writer;\nimport javax.tools.JavaFileObject;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\n/**\n * Regenerates test resources for annotation compiler tests when the {@link\n * Util#REGENERATE_TEST_RESOURCES_PROPERTY_NAME} property is set to the directory containing the\n * project.\n *\n * <p>This can easily be used via gradle by running: {@code ./gradlew\n * :annotation:compiler:test:regenerateTestResources }\n *\n * <p>Our regenerate task will set the appropriate environment variables that will allow the logic\n * here to succeed. When running the tests normally, this class will do nothing.\n */\npublic final class RegenerateResourcesRule implements TestRule {\n\n  private CompilationProvider test;\n\n  public RegenerateResourcesRule(CompilationProvider test) {\n    this.test = test;\n  }\n\n  @Override\n  public Statement apply(final Statement base, final Description description) {\n    return new Statement() {\n      @Override\n      public void evaluate() throws Throwable {\n        try {\n          base.evaluate();\n        } catch (AssertionError e) {\n          String projectRoot = Util.getProjectRootIfRegeneratingTestResources();\n          if (projectRoot == null || description.getAnnotation(ReferencedResource.class) != null) {\n            throw e;\n          }\n          updateResourceFile(e, projectRoot, description);\n        }\n      }\n    };\n  }\n\n  private void updateResourceFile(\n      AssertionError e, @NonNull String projectDirectory, Description description) {\n    String testClassName = test.getClass().getSimpleName();\n    String testFileName = parseFileNameFromMessage(e);\n    String testDirectory = projectDirectory + \"/src/test/resources/\" + testClassName;\n    String subDirectorySegment =\n        description.getAnnotation(SubDirectory.class) != null\n            ? description.getAnnotation(SubDirectory.class).value() + \"/\"\n            : \"\";\n\n    File expectedDirectory = new File(testDirectory + \"/\" + subDirectorySegment);\n    if (!expectedDirectory.exists() && !expectedDirectory.mkdirs()) {\n      throw new IllegalStateException(\n          \"Failed to generate expected directory: \" + expectedDirectory);\n    }\n    if (!expectedDirectory.isDirectory()) {\n      throw new IllegalStateException(\n          \"Expected a directory, but found a file: \" + expectedDirectory);\n    }\n\n    File expectedFile = new File(expectedDirectory, testFileName);\n    Writer writer = null;\n    try {\n      writer = new FileWriter(expectedFile);\n      writer.write(asUnixChars(parseActual(testFileName)).toString());\n      writer.close();\n    } catch (IOException e1) {\n      throw new RuntimeException(\"Failed to regenerate test file\", e1);\n    } finally {\n      if (writer != null) {\n        try {\n          writer.close();\n        } catch (IOException exception) {\n          // Ignore.\n        }\n      }\n    }\n  }\n\n  private String parseActual(String fileName) {\n    for (JavaFileObject javaFileObject : test.getCompilation().generatedSourceFiles()) {\n      if (javaFileObject.getName().contains(fileName)) {\n        try {\n          return javaFileObject.getCharContent(true).toString();\n        } catch (IOException e) {\n          throw new IllegalStateException(e);\n        }\n      }\n    }\n    throw new IllegalStateException(\"Failed to find source file for name: \" + fileName);\n  }\n\n  // Parses </SOURCE_OUTPUT/com/bumptech/glide/test/GlideOptions.java> to GlideOptions.java.\n  private static String parseFileNameFromMessage(AssertionError e) {\n    String message = e.getMessage();\n    int firstGreaterThanIndex = message.indexOf('>');\n    String substring = message.substring(0, firstGreaterThanIndex);\n    int lastForwardSlashIndex = substring.lastIndexOf('/');\n    return substring.substring(lastForwardSlashIndex + 1, substring.length());\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/test/SubDirectory.java",
    "content": "package com.bumptech.glide.annotation.compiler.test;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Indicates the subdirectory for a particular test that contains the test resource(s) used for the\n * method.\n *\n * <p>Used both by tests to extract the correct subdirectory and by the {@link\n * RegenerateResourcesRule} for the same purpose.\n */\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface SubDirectory {\n  String value();\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/test/TestDescription.java",
    "content": "package com.bumptech.glide.annotation.compiler.test;\n\nimport org.junit.rules.TestWatcher;\nimport org.junit.runner.Description;\n\n/**\n * Exposes the {@link Description} for the current test, similar to {@link\n * org.junit.rules.TestName}.\n */\npublic final class TestDescription extends TestWatcher {\n  private Description description;\n\n  @Override\n  protected void starting(Description description) {\n    this.description = description;\n  }\n\n  public Description getDescription() {\n    return description;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/java/com/bumptech/glide/annotation/compiler/test/Util.java",
    "content": "package com.bumptech.glide.annotation.compiler.test;\n\nimport com.google.testing.compile.JavaFileObjects;\nimport javax.tools.JavaFileObject;\n\n/** Test utilities. */\npublic final class Util {\n  private static final String REGENERATE_TEST_RESOURCES_PROPERTY_NAME =\n      \"com.bumptech.glide.annotation.compiler.test.regenerate.path\";\n  private static final String GLIDE_PACKAGE_NAME = \"com.bumptech.glide\";\n  private static final String SUB_PACKAGE_NAME = qualified(GLIDE_PACKAGE_NAME, \"test\");\n  private static final String ANNOTATION_PACKAGE_NAME = \"com.bumptech.glide.annotation.compiler\";\n  private static final String DEFAULT_APP_DIR_NAME = \"EmptyAppGlideModuleTest\";\n  private static final String DEFAULT_LIBRARY_DIR_NAME = \"EmptyLibraryGlideModuleTest\";\n\n  /**\n   * Hardcoded file separator to workaround {@code JavaFileObjects.forResource(...)} defaulting to\n   * the unix one.\n   */\n  private static final String FILE_SEPARATOR = \"/\";\n\n  private static final String LINE_SEPARATOR = \"\\n\";\n\n  private Util() {\n    // Utility class.\n  }\n\n  /**\n   * Returns the {@code String} from a system property that is expected to contain the project\n   * directory for the module containing these tests or {@code null} if we're not currently\n   * attempting to regenerate test resources.\n   */\n  static String getProjectRootIfRegeneratingTestResources() {\n    return System.getProperty(REGENERATE_TEST_RESOURCES_PROPERTY_NAME);\n  }\n\n  public static JavaFileObject emptyAppModule() {\n    return appResource(\"EmptyAppModule.java\");\n  }\n\n  public static JavaFileObject emptyLibraryModule() {\n    return libraryResource(\"EmptyLibraryModule.java\");\n  }\n\n  public static JavaFileObject appResource(String className) {\n    return forResource(DEFAULT_APP_DIR_NAME, className);\n  }\n\n  public static JavaFileObject libraryResource(String className) {\n    return forResource(DEFAULT_LIBRARY_DIR_NAME, className);\n  }\n\n  public static JavaFileObject forResource(String directoryName, String name) {\n    try {\n      return JavaFileObjects.forResource(directoryName + FILE_SEPARATOR + name);\n    } catch (IllegalArgumentException e) {\n      // IllegalArgumentException will be thrown if the resource is missing. If we're trying to\n      // generate test resources for a new test, we want to avoid this exception because it does not\n      // contain any expected output that we can write to a file. By returning an empty file, we\n      // avoid the exception and get the output from our comparison tests that we can then write\n      // out.\n      // If we're not regenerating test resources, we should throw the normal exception.\n      if (getProjectRootIfRegeneratingTestResources() != null) {\n        return JavaFileObjects.forSourceString(\"com.bumptech.test.empty\", \"\");\n      }\n      throw e;\n    }\n  }\n\n  public static String annotation(String className) {\n    return qualified(ANNOTATION_PACKAGE_NAME, className);\n  }\n\n  public static String subpackage(String className) {\n    return qualified(SUB_PACKAGE_NAME, className);\n  }\n\n  public static String glide(String className) {\n    return qualified(GLIDE_PACKAGE_NAME, className);\n  }\n\n  public static CharSequence asUnixChars(CharSequence chars) {\n    return chars.toString().replace(System.lineSeparator(), LINE_SEPARATOR);\n  }\n\n  private static String qualified(String packageName, String className) {\n    return packageName + '.' + className;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithExcludesTest/AppModuleWithExcludes.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.Excludes;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\n\n@GlideModule\n@Excludes(EmptyLibraryModule.class)\npublic final class AppModuleWithExcludes extends AppGlideModule {}"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithExcludesTest/GeneratedAppGlideModuleImpl.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.test.AppModuleWithExcludes;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@SuppressWarnings(\"deprecation\")\nfinal class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {\n  private final AppModuleWithExcludes appGlideModule;\n\n  public GeneratedAppGlideModuleImpl(Context context) {\n    appGlideModule = new AppModuleWithExcludes();\n    if (Log.isLoggable(\"Glide\", Log.DEBUG)) {\n      Log.d(\"Glide\", \"Discovered AppGlideModule from annotation: com.bumptech.glide.test.AppModuleWithExcludes\");\n      Log.d(\"Glide\", \"AppGlideModule excludes LibraryGlideModule from annotation: com.bumptech.glide.test.EmptyLibraryModule\");\n    }\n  }\n\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    appGlideModule.applyOptions(context, builder);\n  }\n\n  @Override\n  public void registerComponents(@NonNull Context context, @NonNull Glide glide,\n      @NonNull Registry registry) {\n    appGlideModule.registerComponents(context, glide, registry);\n  }\n\n  @Override\n  public boolean isManifestParsingEnabled() {\n    return appGlideModule.isManifestParsingEnabled();\n  }\n\n  @Override\n  @NonNull\n  public Set<Class<?>> getExcludedModuleClasses() {\n    Set<Class<?>> excludedClasses = new HashSet<Class<?>>();\n    excludedClasses.add(com.bumptech.glide.test.EmptyLibraryModule.class);\n    return excludedClasses;\n  }\n\n  @Override\n  @NonNull\n  GeneratedRequestManagerFactory getRequestManagerFactory() {\n    return new GeneratedRequestManagerFactory();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithLibraryInPackageTest/AppModuleWithLibraryInPackage.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.Excludes;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.test._package.LibraryModuleInPackage;\n\n@GlideModule\n@Excludes(LibraryModuleInPackage.class)\npublic final class AppModuleWithLibraryInPackage extends AppGlideModule {}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithLibraryInPackageTest/GeneratedAppGlideModuleImpl.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.test.AppModuleWithLibraryInPackage;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@SuppressWarnings(\"deprecation\")\nfinal class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {\n  private final AppModuleWithLibraryInPackage appGlideModule;\n\n  public GeneratedAppGlideModuleImpl(Context context) {\n    appGlideModule = new AppModuleWithLibraryInPackage();\n    if (Log.isLoggable(\"Glide\", Log.DEBUG)) {\n      Log.d(\"Glide\", \"Discovered AppGlideModule from annotation: com.bumptech.glide.test.AppModuleWithLibraryInPackage\");\n      Log.d(\"Glide\", \"AppGlideModule excludes LibraryGlideModule from annotation: com.bumptech.glide.test._package.LibraryModuleInPackage\");\n    }\n  }\n\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    appGlideModule.applyOptions(context, builder);\n  }\n\n  @Override\n  public void registerComponents(@NonNull Context context, @NonNull Glide glide,\n      @NonNull Registry registry) {\n    appGlideModule.registerComponents(context, glide, registry);\n  }\n\n  @Override\n  public boolean isManifestParsingEnabled() {\n    return appGlideModule.isManifestParsingEnabled();\n  }\n\n  @Override\n  @NonNull\n  public Set<Class<?>> getExcludedModuleClasses() {\n    Set<Class<?>> excludedClasses = new HashSet<Class<?>>();\n    excludedClasses.add(com.bumptech.glide.test._package.LibraryModuleInPackage.class);\n    return excludedClasses;\n  }\n\n  @Override\n  @NonNull\n  GeneratedRequestManagerFactory getRequestManagerFactory() {\n    return new GeneratedRequestManagerFactory();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithLibraryInPackageTest/LibraryModuleInPackage.java",
    "content": "// _ in the name is important otherwise everything would work\npackage com.bumptech.glide.test._package;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\n\n@GlideModule\npublic final class LibraryModuleInPackage extends LibraryGlideModule {}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithMultipleExcludesTest/AppModuleWithMultipleExcludes.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.Excludes;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\n\n@GlideModule\n@Excludes({EmptyLibraryModule1.class, EmptyLibraryModule2.class})\npublic final class AppModuleWithMultipleExcludes extends AppGlideModule {}"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithMultipleExcludesTest/EmptyLibraryModule1.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\n\n@GlideModule\npublic final class EmptyLibraryModule1 extends LibraryGlideModule {}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithMultipleExcludesTest/EmptyLibraryModule2.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\n\n@GlideModule\npublic final class EmptyLibraryModule2 extends LibraryGlideModule {}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/AppGlideModuleWithMultipleExcludesTest/GeneratedAppGlideModuleImpl.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.test.AppModuleWithMultipleExcludes;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@SuppressWarnings(\"deprecation\")\nfinal class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {\n  private final AppModuleWithMultipleExcludes appGlideModule;\n\n  public GeneratedAppGlideModuleImpl(Context context) {\n    appGlideModule = new AppModuleWithMultipleExcludes();\n    if (Log.isLoggable(\"Glide\", Log.DEBUG)) {\n      Log.d(\"Glide\", \"Discovered AppGlideModule from annotation: com.bumptech.glide.test.AppModuleWithMultipleExcludes\");\n      Log.d(\"Glide\", \"AppGlideModule excludes LibraryGlideModule from annotation: com.bumptech.glide.test.EmptyLibraryModule1\");\n      Log.d(\"Glide\", \"AppGlideModule excludes LibraryGlideModule from annotation: com.bumptech.glide.test.EmptyLibraryModule2\");\n    }\n  }\n\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    appGlideModule.applyOptions(context, builder);\n  }\n\n  @Override\n  public void registerComponents(@NonNull Context context, @NonNull Glide glide,\n      @NonNull Registry registry) {\n    appGlideModule.registerComponents(context, glide, registry);\n  }\n\n  @Override\n  public boolean isManifestParsingEnabled() {\n    return appGlideModule.isManifestParsingEnabled();\n  }\n\n  @Override\n  @NonNull\n  public Set<Class<?>> getExcludedModuleClasses() {\n    Set<Class<?>> excludedClasses = new HashSet<Class<?>>();\n    excludedClasses.add(com.bumptech.glide.test.EmptyLibraryModule1.class);\n    excludedClasses.add(com.bumptech.glide.test.EmptyLibraryModule2.class);\n    return excludedClasses;\n  }\n\n  @Override\n  @NonNull\n  GeneratedRequestManagerFactory getRequestManagerFactory() {\n    return new GeneratedRequestManagerFactory();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyAppAndLibraryGlideModulesTest/GeneratedAppGlideModuleImpl.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.test.EmptyAppModule;\nimport com.bumptech.glide.test.EmptyLibraryModule;\nimport java.util.Collections;\nimport java.util.Set;\n\n@SuppressWarnings(\"deprecation\")\nfinal class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {\n  private final EmptyAppModule appGlideModule;\n\n  public GeneratedAppGlideModuleImpl(Context context) {\n    appGlideModule = new EmptyAppModule();\n    if (Log.isLoggable(\"Glide\", Log.DEBUG)) {\n      Log.d(\"Glide\", \"Discovered AppGlideModule from annotation: com.bumptech.glide.test.EmptyAppModule\");\n      Log.d(\"Glide\", \"Discovered LibraryGlideModule from annotation: com.bumptech.glide.test.EmptyLibraryModule\");\n    }\n  }\n\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    appGlideModule.applyOptions(context, builder);\n  }\n\n  @Override\n  public void registerComponents(@NonNull Context context, @NonNull Glide glide,\n      @NonNull Registry registry) {\n    new EmptyLibraryModule().registerComponents(context, glide, registry);\n    appGlideModule.registerComponents(context, glide, registry);\n  }\n\n  @Override\n  public boolean isManifestParsingEnabled() {\n    return appGlideModule.isManifestParsingEnabled();\n  }\n\n  @Override\n  @NonNull\n  public Set<Class<?>> getExcludedModuleClasses() {\n    return Collections.emptySet();\n  }\n\n  @Override\n  @NonNull\n  GeneratedRequestManagerFactory getRequestManagerFactory() {\n    return new GeneratedRequestManagerFactory();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyAppGlideModuleTest/EmptyAppModule.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\n\n@GlideModule\npublic final class EmptyAppModule extends AppGlideModule {}"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyAppGlideModuleTest/GeneratedAppGlideModuleImpl.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.test.EmptyAppModule;\nimport java.util.Collections;\nimport java.util.Set;\n\n@SuppressWarnings(\"deprecation\")\nfinal class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {\n  private final EmptyAppModule appGlideModule;\n\n  public GeneratedAppGlideModuleImpl(Context context) {\n    appGlideModule = new EmptyAppModule();\n    if (Log.isLoggable(\"Glide\", Log.DEBUG)) {\n      Log.d(\"Glide\", \"Discovered AppGlideModule from annotation: com.bumptech.glide.test.EmptyAppModule\");\n    }\n  }\n\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    appGlideModule.applyOptions(context, builder);\n  }\n\n  @Override\n  public void registerComponents(@NonNull Context context, @NonNull Glide glide,\n      @NonNull Registry registry) {\n    appGlideModule.registerComponents(context, glide, registry);\n  }\n\n  @Override\n  public boolean isManifestParsingEnabled() {\n    return appGlideModule.isManifestParsingEnabled();\n  }\n\n  @Override\n  @NonNull\n  public Set<Class<?>> getExcludedModuleClasses() {\n    return Collections.emptySet();\n  }\n\n  @Override\n  @NonNull\n  GeneratedRequestManagerFactory getRequestManagerFactory() {\n    return new GeneratedRequestManagerFactory();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyAppGlideModuleTest/GeneratedRequestManagerFactory.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.manager.Lifecycle;\nimport com.bumptech.glide.manager.RequestManagerRetriever;\nimport com.bumptech.glide.manager.RequestManagerTreeNode;\nimport com.bumptech.glide.test.GlideRequests;\n\n/**\n * Generated code, do not modify\n */\nfinal class GeneratedRequestManagerFactory implements RequestManagerRetriever.RequestManagerFactory {\n  @Override\n  @NonNull\n  public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle,\n      @NonNull RequestManagerTreeNode treeNode, @NonNull Context context) {\n    return new GlideRequests(glide, lifecycle, treeNode, context);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyAppGlideModuleTest/GlideApp.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport android.view.View;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport java.io.File;\n\n/**\n * The entry point for interacting with Glide for Applications\n *\n * <p>Includes all generated APIs from all\n * {@link com.bumptech.glide.annotation.GlideExtension}s in source and dependent libraries.\n *\n * <p>This class is generated and should not be modified\n * @see Glide\n */\npublic final class GlideApp {\n  private GlideApp() {\n  }\n\n  /**\n   * @see Glide#getPhotoCacheDir(Context)\n   */\n  @Nullable\n  public static File getPhotoCacheDir(@NonNull Context context) {\n    return Glide.getPhotoCacheDir(context);\n  }\n\n  /**\n   * @see Glide#getPhotoCacheDir(Context, String)\n   */\n  @Nullable\n  public static File getPhotoCacheDir(@NonNull Context context, @NonNull String string) {\n    return Glide.getPhotoCacheDir(context, string);\n  }\n\n  /**\n   * @see Glide#get(Context)\n   */\n  @NonNull\n  public static Glide get(@NonNull Context context) {\n    return Glide.get(context);\n  }\n\n  /**\n   * @see Glide#init(Glide)\n   */\n  @Deprecated\n  @VisibleForTesting\n  @SuppressLint(\"VisibleForTests\")\n  public static void init(Glide glide) {\n    Glide.init(glide);\n  }\n\n  /**\n   * @see Glide#init(Context, GlideBuilder)\n   */\n  @VisibleForTesting\n  @SuppressLint(\"VisibleForTests\")\n  public static void init(@NonNull Context context, @NonNull GlideBuilder builder) {\n    Glide.init(context, builder);\n  }\n\n  /**\n   * @see Glide#enableHardwareBitmaps()\n   */\n  @VisibleForTesting\n  @SuppressLint(\"VisibleForTests\")\n  public static void enableHardwareBitmaps() {\n    Glide.enableHardwareBitmaps();\n  }\n\n  /**\n   * @see Glide#tearDown()\n   */\n  @VisibleForTesting\n  @SuppressLint(\"VisibleForTests\")\n  public static void tearDown() {\n    Glide.tearDown();\n  }\n\n  /**\n   * @see Glide#with(Context)\n   */\n  @NonNull\n  public static GlideRequests with(@NonNull Context context) {\n    return (GlideRequests) Glide.with(context);\n  }\n\n  /**\n   * @see Glide#with(Activity)\n   */\n  @Deprecated\n  @NonNull\n  public static GlideRequests with(@NonNull Activity activity) {\n    return (GlideRequests) Glide.with(activity);\n  }\n\n  /**\n   * @see Glide#with(FragmentActivity)\n   */\n  @NonNull\n  public static GlideRequests with(@NonNull FragmentActivity activity) {\n    return (GlideRequests) Glide.with(activity);\n  }\n\n  /**\n   * @see Glide#with(Fragment)\n   */\n  @NonNull\n  public static GlideRequests with(@NonNull Fragment fragment) {\n    return (GlideRequests) Glide.with(fragment);\n  }\n\n  /**\n   * @see Glide#with(Fragment)\n   */\n  @Deprecated\n  @NonNull\n  public static GlideRequests with(@NonNull android.app.Fragment fragment) {\n    return (GlideRequests) Glide.with(fragment);\n  }\n\n  /**\n   * @see Glide#with(View)\n   */\n  @NonNull\n  public static GlideRequests with(@NonNull View view) {\n    return (GlideRequests) Glide.with(view);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyAppGlideModuleTest/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform0;\n\n  private static GlideOptions centerInsideTransform1;\n\n  private static GlideOptions centerCropTransform2;\n\n  private static GlideOptions circleCropTransform3;\n\n  private static GlideOptions noTransformation4;\n\n  private static GlideOptions noAnimation5;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int, int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform0 == null) {\n      GlideOptions.fitCenterTransform0 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform0;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform1 == null) {\n      GlideOptions.centerInsideTransform1 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform2 == null) {\n      GlideOptions.centerCropTransform2 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform2;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform3 == null) {\n      GlideOptions.circleCropTransform3 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation4 == null) {\n      GlideOptions.noTransformation4 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation4;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation5 == null) {\n      GlideOptions.noAnimation5 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation5;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) super.override(width, height);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerCrop() {\n    return (GlideOptions) super.centerCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyAppGlideModuleTest/GlideRequest.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Contains all public methods from {@link RequestBuilder<TranscodeType>}, all options from\n * {@link com.bumptech.glide.request.RequestOptions} and all generated options from\n * {@link com.bumptech.glide.annotation.GlideOption} in annotated methods in\n * {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * <p>Generated code, do not modify.\n *\n * @see RequestBuilder<TranscodeType>\n * @see com.bumptech.glide.request.RequestOptions\n */\n@SuppressWarnings({\n    \"unused\",\n    \"deprecation\"\n})\npublic class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {\n  GlideRequest(@NonNull Class<TranscodeType> transcodeClass, @NonNull RequestBuilder<?> other) {\n    super(transcodeClass, other);\n  }\n\n  GlideRequest(@NonNull Glide glide, @NonNull RequestManager requestManager,\n      @NonNull Class<TranscodeType> transcodeClass, @NonNull Context context) {\n    super(glide, requestManager ,transcodeClass, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  protected GlideRequest<File> getDownloadOnlyRequest() {\n    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * @see GlideOptions#sizeMultiplier(float)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideRequest<TranscodeType>) super.sizeMultiplier(value);\n  }\n\n  /**\n   * @see GlideOptions#useUnlimitedSourceGeneratorsPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#useAnimationPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useAnimationPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useAnimationPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#onlyRetrieveFromCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> onlyRetrieveFromCache(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.onlyRetrieveFromCache(flag);\n  }\n\n  /**\n   * @see GlideOptions#diskCacheStrategy(DiskCacheStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see GlideOptions#priority(Priority)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> priority(@NonNull Priority priority) {\n    return (GlideRequest<TranscodeType>) super.priority(priority);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.placeholder(drawable);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.placeholder(id);\n  }\n\n  /**\n   * @see GlideOptions#fallback(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.fallback(drawable);\n  }\n\n  /**\n   * @see GlideOptions#fallback(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.fallback(id);\n  }\n\n  /**\n   * @see GlideOptions#error(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.error(drawable);\n  }\n\n  /**\n   * @see GlideOptions#error(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.error(id);\n  }\n\n  /**\n   * @see GlideOptions#theme(Resources.Theme)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> theme(@Nullable Resources.Theme theme) {\n    return (GlideRequest<TranscodeType>) super.theme(theme);\n  }\n\n  /**\n   * @see GlideOptions#skipMemoryCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> skipMemoryCache(boolean skip) {\n    return (GlideRequest<TranscodeType>) super.skipMemoryCache(skip);\n  }\n\n  /**\n   * @see GlideOptions#override(int, int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int width, int height) {\n    return (GlideRequest<TranscodeType>) super.override(width, height);\n  }\n\n  /**\n   * @see GlideOptions#override(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int size) {\n    return (GlideRequest<TranscodeType>) super.override(size);\n  }\n\n  /**\n   * @see GlideOptions#signature(Key)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> signature(@NonNull Key key) {\n    return (GlideRequest<TranscodeType>) super.signature(key);\n  }\n\n  /**\n   * @see GlideOptions#set(Option<Y>, Y)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideRequest<TranscodeType>) super.set(option, y);\n  }\n\n  /**\n   * @see GlideOptions#decode(Class<?>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> decode(@NonNull Class<?> clazz) {\n    return (GlideRequest<TranscodeType>) super.decode(clazz);\n  }\n\n  /**\n   * @see GlideOptions#encodeFormat(Bitmap.CompressFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideRequest<TranscodeType>) super.encodeFormat(format);\n  }\n\n  /**\n   * @see GlideOptions#encodeQuality(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideRequest<TranscodeType>) super.encodeQuality(value);\n  }\n\n  /**\n   * @see GlideOptions#frame(long)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> frame(@IntRange(from = 0) long value) {\n    return (GlideRequest<TranscodeType>) super.frame(value);\n  }\n\n  /**\n   * @see GlideOptions#format(DecodeFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> format(@NonNull DecodeFormat format) {\n    return (GlideRequest<TranscodeType>) super.format(format);\n  }\n\n  /**\n   * @see GlideOptions#disallowHardwareConfig()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> disallowHardwareConfig() {\n    return (GlideRequest<TranscodeType>) super.disallowHardwareConfig();\n  }\n\n  /**\n   * @see GlideOptions#downsample(DownsampleStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.downsample(strategy);\n  }\n\n  /**\n   * @see GlideOptions#timeout(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> timeout(@IntRange(from = 0) int value) {\n    return (GlideRequest<TranscodeType>) super.timeout(value);\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterCrop();\n  }\n\n  /**\n   * @see GlideOptions#centerCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerCrop() {\n    return (GlideRequest<TranscodeType>) super.centerCrop();\n  }\n\n  /**\n   * @see GlideOptions#optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalFitCenter() {\n    return (GlideRequest<TranscodeType>) super.optionalFitCenter();\n  }\n\n  /**\n   * @see GlideOptions#fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fitCenter() {\n    return (GlideRequest<TranscodeType>) super.fitCenter();\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterInside() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterInside();\n  }\n\n  /**\n   * @see GlideOptions#centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerInside() {\n    return (GlideRequest<TranscodeType>) super.centerInside();\n  }\n\n  /**\n   * @see GlideOptions#optionalCircleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCircleCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCircleCrop();\n  }\n\n  /**\n   * @see GlideOptions#circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> circleCrop() {\n    return (GlideRequest<TranscodeType>) super.circleCrop();\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>[])\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transform(transformations);\n  }\n\n  /**\n   * @see GlideOptions#transforms(Transformation<Bitmap>[])\n   */\n  @Deprecated\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transforms(\n      @NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transforms(transformations);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalTransform(\n      @NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#dontTransform()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontTransform() {\n    return (GlideRequest<TranscodeType>) super.dontTransform();\n  }\n\n  /**\n   * @see GlideOptions#dontAnimate()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontAnimate() {\n    return (GlideRequest<TranscodeType>) super.dontAnimate();\n  }\n\n  /**\n   * @see GlideOptions#lock()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> lock() {\n    return (GlideRequest<TranscodeType>) super.lock();\n  }\n\n  /**\n   * @see GlideOptions#autoClone()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> autoClone() {\n    return (GlideRequest<TranscodeType>) super.autoClone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideRequest<TranscodeType>) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> options) {\n    return (GlideRequest<TranscodeType>) super.transition(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> listener(@Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.listener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.addListener(listener);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequest<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.error(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(Object o) {\n    return (GlideRequest<TranscodeType>) super.error(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public final GlideRequest<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... builders) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builders);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable List<RequestBuilder<TranscodeType>> list) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(list);\n  }\n\n  @Override\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(float sizeMultiplier) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(sizeMultiplier);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Object o) {\n    return (GlideRequest<TranscodeType>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<TranscodeType>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable String string) {\n    return (GlideRequest<TranscodeType>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Uri uri) {\n    return (GlideRequest<TranscodeType>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable File file) {\n    return (GlideRequest<TranscodeType>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<TranscodeType>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable URL url) {\n    return (GlideRequest<TranscodeType>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable byte[] bytes) {\n    return (GlideRequest<TranscodeType>) super.load(bytes);\n  }\n\n  @Override\n  @CheckResult\n  public GlideRequest<TranscodeType> clone() {\n    return (GlideRequest<TranscodeType>) super.clone();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyAppGlideModuleTest/GlideRequests.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.manager.Lifecycle;\nimport com.bumptech.glide.manager.RequestManagerTreeNode;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport java.io.File;\nimport java.net.URL;\n\n/**\n * Includes all additions from methods in {@link com.bumptech.glide.annotation.GlideExtension}s\n * annotated with {@link com.bumptech.glide.annotation.GlideType}\n *\n * <p>Generated code, do not modify\n */\n@SuppressWarnings(\"deprecation\")\npublic class GlideRequests extends RequestManager {\n  public GlideRequests(@NonNull Glide glide, @NonNull Lifecycle lifecycle,\n      @NonNull RequestManagerTreeNode treeNode, @NonNull Context context) {\n    super(glide, lifecycle, treeNode, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  public <ResourceType> GlideRequest<ResourceType> as(@NonNull Class<ResourceType> resourceClass) {\n    return new GlideRequest<>(glide, this, resourceClass, context);\n  }\n\n  @Override\n  @NonNull\n  public synchronized GlideRequests applyDefaultRequestOptions(@NonNull RequestOptions options) {\n    return (GlideRequests) super.applyDefaultRequestOptions(options);\n  }\n\n  @Override\n  @NonNull\n  public synchronized GlideRequests setDefaultRequestOptions(@NonNull RequestOptions options) {\n    return (GlideRequests) super.setDefaultRequestOptions(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequests addDefaultRequestListener(RequestListener<Object> listener) {\n    return (GlideRequests) super.addDefaultRequestListener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Bitmap> asBitmap() {\n    return (GlideRequest<Bitmap>) super.asBitmap();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<GifDrawable> asGif() {\n    return (GlideRequest<GifDrawable>) super.asGif();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> asDrawable() {\n    return (GlideRequest<Drawable>) super.asDrawable();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<Drawable>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable Drawable drawable) {\n    return (GlideRequest<Drawable>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable String string) {\n    return (GlideRequest<Drawable>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable Uri uri) {\n    return (GlideRequest<Drawable>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable File file) {\n    return (GlideRequest<Drawable>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<Drawable>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable URL url) {\n    return (GlideRequest<Drawable>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable byte[] bytes) {\n    return (GlideRequest<Drawable>) super.load(bytes);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable Object o) {\n    return (GlideRequest<Drawable>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<File> downloadOnly() {\n    return (GlideRequest<File>) super.downloadOnly();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<File> download(@Nullable Object o) {\n    return (GlideRequest<File>) super.download(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<File> asFile() {\n    return (GlideRequest<File>) super.asFile();\n  }\n\n  @Override\n  protected void setRequestOptions(@NonNull RequestOptions toSet) {\n    if (toSet instanceof com.bumptech.glide.test.GlideOptions) {\n      super.setRequestOptions(toSet);\n    } else {\n      super.setRequestOptions(new com.bumptech.glide.test.GlideOptions().apply(toSet));\n    }\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyLibraryGlideModuleTest/EmptyLibraryModule.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\n\n@GlideModule\npublic final class EmptyLibraryModule extends LibraryGlideModule {}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/EmptyLibraryGlideModuleTest/GlideIndexer_GlideModule_com_bumptech_glide_test_EmptyLibraryModule.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\n@Index(\n    modules = \"com.bumptech.glide.test.EmptyLibraryModule\"\n)\npublic class GlideIndexer_GlideModule_com_bumptech_glide_test_EmptyLibraryModule {\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/MemoizeStaticMethod/Extension.java",
    "content": "package com.bumptech.glide.test;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.request.BaseRequestOptions;\n\n@GlideExtension\npublic final class Extension {\n\n  private Extension() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideOption(memoizeStaticMethod = true)\n  public static BaseRequestOptions<?> test(BaseRequestOptions<?> requestOptions) {\n    return requestOptions.centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/MemoizeStaticMethod/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n * @see Extension\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform1;\n\n  private static GlideOptions centerInsideTransform2;\n\n  private static GlideOptions centerCropTransform3;\n\n  private static GlideOptions circleCropTransform4;\n\n  private static GlideOptions noTransformation5;\n\n  private static GlideOptions noAnimation6;\n\n  private static GlideOptions testOf0;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int, int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform1 == null) {\n      GlideOptions.fitCenterTransform1 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform2 == null) {\n      GlideOptions.centerInsideTransform2 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform2;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform3 == null) {\n      GlideOptions.centerCropTransform3 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform4 == null) {\n      GlideOptions.circleCropTransform4 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform4;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation5 == null) {\n      GlideOptions.noTransformation5 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation5;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation6 == null) {\n      GlideOptions.noAnimation6 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation6;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) super.override(width, height);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerCrop() {\n    return (GlideOptions) super.centerCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n\n  /**\n   * @see Extension#test(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideOptions test() {\n    return (GlideOptions) Extension.test(this);\n  }\n\n  /**\n   * @see Extension#test(BaseRequestOptions)\n   */\n  @CheckResult\n  public static GlideOptions testOf() {\n    if (GlideOptions.testOf0 == null) {\n      GlideOptions.testOf0 =\n          new GlideOptions().test().autoClone();\n    }\n    return GlideOptions.testOf0;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/MemoizeStaticMethod/GlideRequest.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Contains all public methods from {@link RequestBuilder<TranscodeType>}, all options from\n * {@link com.bumptech.glide.request.RequestOptions} and all generated options from\n * {@link com.bumptech.glide.annotation.GlideOption} in annotated methods in\n * {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * <p>Generated code, do not modify.\n *\n * @see RequestBuilder<TranscodeType>\n * @see com.bumptech.glide.request.RequestOptions\n */\n@SuppressWarnings({\n    \"unused\",\n    \"deprecation\"\n})\npublic class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {\n  GlideRequest(@NonNull Class<TranscodeType> transcodeClass, @NonNull RequestBuilder<?> other) {\n    super(transcodeClass, other);\n  }\n\n  GlideRequest(@NonNull Glide glide, @NonNull RequestManager requestManager,\n      @NonNull Class<TranscodeType> transcodeClass, @NonNull Context context) {\n    super(glide, requestManager ,transcodeClass, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  protected GlideRequest<File> getDownloadOnlyRequest() {\n    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * @see GlideOptions#sizeMultiplier(float)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideRequest<TranscodeType>) super.sizeMultiplier(value);\n  }\n\n  /**\n   * @see GlideOptions#useUnlimitedSourceGeneratorsPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#useAnimationPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useAnimationPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useAnimationPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#onlyRetrieveFromCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> onlyRetrieveFromCache(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.onlyRetrieveFromCache(flag);\n  }\n\n  /**\n   * @see GlideOptions#diskCacheStrategy(DiskCacheStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see GlideOptions#priority(Priority)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> priority(@NonNull Priority priority) {\n    return (GlideRequest<TranscodeType>) super.priority(priority);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.placeholder(drawable);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.placeholder(id);\n  }\n\n  /**\n   * @see GlideOptions#fallback(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.fallback(drawable);\n  }\n\n  /**\n   * @see GlideOptions#fallback(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.fallback(id);\n  }\n\n  /**\n   * @see GlideOptions#error(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.error(drawable);\n  }\n\n  /**\n   * @see GlideOptions#error(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.error(id);\n  }\n\n  /**\n   * @see GlideOptions#theme(Resources.Theme)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> theme(@Nullable Resources.Theme theme) {\n    return (GlideRequest<TranscodeType>) super.theme(theme);\n  }\n\n  /**\n   * @see GlideOptions#skipMemoryCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> skipMemoryCache(boolean skip) {\n    return (GlideRequest<TranscodeType>) super.skipMemoryCache(skip);\n  }\n\n  /**\n   * @see GlideOptions#override(int, int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int width, int height) {\n    return (GlideRequest<TranscodeType>) super.override(width, height);\n  }\n\n  /**\n   * @see GlideOptions#override(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int size) {\n    return (GlideRequest<TranscodeType>) super.override(size);\n  }\n\n  /**\n   * @see GlideOptions#signature(Key)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> signature(@NonNull Key key) {\n    return (GlideRequest<TranscodeType>) super.signature(key);\n  }\n\n  /**\n   * @see GlideOptions#set(Option<Y>, Y)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideRequest<TranscodeType>) super.set(option, y);\n  }\n\n  /**\n   * @see GlideOptions#decode(Class<?>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> decode(@NonNull Class<?> clazz) {\n    return (GlideRequest<TranscodeType>) super.decode(clazz);\n  }\n\n  /**\n   * @see GlideOptions#encodeFormat(Bitmap.CompressFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideRequest<TranscodeType>) super.encodeFormat(format);\n  }\n\n  /**\n   * @see GlideOptions#encodeQuality(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideRequest<TranscodeType>) super.encodeQuality(value);\n  }\n\n  /**\n   * @see GlideOptions#frame(long)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> frame(@IntRange(from = 0) long value) {\n    return (GlideRequest<TranscodeType>) super.frame(value);\n  }\n\n  /**\n   * @see GlideOptions#format(DecodeFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> format(@NonNull DecodeFormat format) {\n    return (GlideRequest<TranscodeType>) super.format(format);\n  }\n\n  /**\n   * @see GlideOptions#disallowHardwareConfig()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> disallowHardwareConfig() {\n    return (GlideRequest<TranscodeType>) super.disallowHardwareConfig();\n  }\n\n  /**\n   * @see GlideOptions#downsample(DownsampleStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.downsample(strategy);\n  }\n\n  /**\n   * @see GlideOptions#timeout(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> timeout(@IntRange(from = 0) int value) {\n    return (GlideRequest<TranscodeType>) super.timeout(value);\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterCrop();\n  }\n\n  /**\n   * @see GlideOptions#centerCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerCrop() {\n    return (GlideRequest<TranscodeType>) super.centerCrop();\n  }\n\n  /**\n   * @see GlideOptions#optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalFitCenter() {\n    return (GlideRequest<TranscodeType>) super.optionalFitCenter();\n  }\n\n  /**\n   * @see GlideOptions#fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fitCenter() {\n    return (GlideRequest<TranscodeType>) super.fitCenter();\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterInside() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterInside();\n  }\n\n  /**\n   * @see GlideOptions#centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerInside() {\n    return (GlideRequest<TranscodeType>) super.centerInside();\n  }\n\n  /**\n   * @see GlideOptions#optionalCircleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCircleCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCircleCrop();\n  }\n\n  /**\n   * @see GlideOptions#circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> circleCrop() {\n    return (GlideRequest<TranscodeType>) super.circleCrop();\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>[])\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transform(transformations);\n  }\n\n  /**\n   * @see GlideOptions#transforms(Transformation<Bitmap>[])\n   */\n  @Deprecated\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transforms(\n      @NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transforms(transformations);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalTransform(\n      @NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#dontTransform()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontTransform() {\n    return (GlideRequest<TranscodeType>) super.dontTransform();\n  }\n\n  /**\n   * @see GlideOptions#dontAnimate()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontAnimate() {\n    return (GlideRequest<TranscodeType>) super.dontAnimate();\n  }\n\n  /**\n   * @see GlideOptions#lock()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> lock() {\n    return (GlideRequest<TranscodeType>) super.lock();\n  }\n\n  /**\n   * @see GlideOptions#autoClone()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> autoClone() {\n    return (GlideRequest<TranscodeType>) super.autoClone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideRequest<TranscodeType>) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> options) {\n    return (GlideRequest<TranscodeType>) super.transition(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> listener(@Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.listener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.addListener(listener);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequest<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.error(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(Object o) {\n    return (GlideRequest<TranscodeType>) super.error(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public final GlideRequest<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... builders) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builders);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable List<RequestBuilder<TranscodeType>> list) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(list);\n  }\n\n  @Override\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(float sizeMultiplier) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(sizeMultiplier);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Object o) {\n    return (GlideRequest<TranscodeType>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<TranscodeType>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable String string) {\n    return (GlideRequest<TranscodeType>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Uri uri) {\n    return (GlideRequest<TranscodeType>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable File file) {\n    return (GlideRequest<TranscodeType>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<TranscodeType>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable URL url) {\n    return (GlideRequest<TranscodeType>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable byte[] bytes) {\n    return (GlideRequest<TranscodeType>) super.load(bytes);\n  }\n\n  @Override\n  @CheckResult\n  public GlideRequest<TranscodeType> clone() {\n    return (GlideRequest<TranscodeType>) super.clone();\n  }\n\n  /**\n   * @see Extension#test(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideRequest<TranscodeType> test() {\n    return (GlideRequest<TranscodeType>) Extension.test(this);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideExtend/Extension.java",
    "content": "package com.bumptech.glide.test;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.request.BaseRequestOptions;\n\n@GlideExtension\npublic final class Extension {\n\n  private Extension() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideOption(override = GlideOption.OVERRIDE_EXTEND)\n  public static BaseRequestOptions<?> centerCrop(BaseRequestOptions<?> requestOptions) {\n    return requestOptions.centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideExtend/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n * @see Extension\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform0;\n\n  private static GlideOptions centerInsideTransform1;\n\n  private static GlideOptions centerCropTransform2;\n\n  private static GlideOptions circleCropTransform3;\n\n  private static GlideOptions noTransformation4;\n\n  private static GlideOptions noAnimation5;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int, int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform0 == null) {\n      GlideOptions.fitCenterTransform0 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform0;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform1 == null) {\n      GlideOptions.centerInsideTransform1 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform2 == null) {\n      GlideOptions.centerCropTransform2 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform2;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform3 == null) {\n      GlideOptions.circleCropTransform3 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation4 == null) {\n      GlideOptions.noTransformation4 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation4;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation5 == null) {\n      GlideOptions.noAnimation5 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation5;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) super.override(width, height);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n\n  /**\n   * @see Extension#centerCrop(BaseRequestOptions)\n   * @see GlideOptions#centerCrop()\n   */\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  @CheckResult\n  @NonNull\n  public GlideOptions centerCrop() {\n    return (GlideOptions) Extension.centerCrop(super.centerCrop());\n  }\n\n  /**\n   * @see Extension#centerCrop(BaseRequestOptions)\n   */\n  @CheckResult\n  public static GlideOptions centerCropOf() {\n    return new GlideOptions().centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideExtend/GlideRequest.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Contains all public methods from {@link RequestBuilder<TranscodeType>}, all options from\n * {@link com.bumptech.glide.request.RequestOptions} and all generated options from\n * {@link com.bumptech.glide.annotation.GlideOption} in annotated methods in\n * {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * <p>Generated code, do not modify.\n *\n * @see RequestBuilder<TranscodeType>\n * @see com.bumptech.glide.request.RequestOptions\n */\n@SuppressWarnings({\n    \"unused\",\n    \"deprecation\"\n})\npublic class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {\n  GlideRequest(@NonNull Class<TranscodeType> transcodeClass, @NonNull RequestBuilder<?> other) {\n    super(transcodeClass, other);\n  }\n\n  GlideRequest(@NonNull Glide glide, @NonNull RequestManager requestManager,\n      @NonNull Class<TranscodeType> transcodeClass, @NonNull Context context) {\n    super(glide, requestManager ,transcodeClass, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  protected GlideRequest<File> getDownloadOnlyRequest() {\n    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * @see GlideOptions#sizeMultiplier(float)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideRequest<TranscodeType>) super.sizeMultiplier(value);\n  }\n\n  /**\n   * @see GlideOptions#useUnlimitedSourceGeneratorsPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#useAnimationPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useAnimationPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useAnimationPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#onlyRetrieveFromCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> onlyRetrieveFromCache(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.onlyRetrieveFromCache(flag);\n  }\n\n  /**\n   * @see GlideOptions#diskCacheStrategy(DiskCacheStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see GlideOptions#priority(Priority)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> priority(@NonNull Priority priority) {\n    return (GlideRequest<TranscodeType>) super.priority(priority);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.placeholder(drawable);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.placeholder(id);\n  }\n\n  /**\n   * @see GlideOptions#fallback(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.fallback(drawable);\n  }\n\n  /**\n   * @see GlideOptions#fallback(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.fallback(id);\n  }\n\n  /**\n   * @see GlideOptions#error(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.error(drawable);\n  }\n\n  /**\n   * @see GlideOptions#error(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.error(id);\n  }\n\n  /**\n   * @see GlideOptions#theme(Resources.Theme)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> theme(@Nullable Resources.Theme theme) {\n    return (GlideRequest<TranscodeType>) super.theme(theme);\n  }\n\n  /**\n   * @see GlideOptions#skipMemoryCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> skipMemoryCache(boolean skip) {\n    return (GlideRequest<TranscodeType>) super.skipMemoryCache(skip);\n  }\n\n  /**\n   * @see GlideOptions#override(int, int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int width, int height) {\n    return (GlideRequest<TranscodeType>) super.override(width, height);\n  }\n\n  /**\n   * @see GlideOptions#override(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int size) {\n    return (GlideRequest<TranscodeType>) super.override(size);\n  }\n\n  /**\n   * @see GlideOptions#signature(Key)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> signature(@NonNull Key key) {\n    return (GlideRequest<TranscodeType>) super.signature(key);\n  }\n\n  /**\n   * @see GlideOptions#set(Option<Y>, Y)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideRequest<TranscodeType>) super.set(option, y);\n  }\n\n  /**\n   * @see GlideOptions#decode(Class<?>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> decode(@NonNull Class<?> clazz) {\n    return (GlideRequest<TranscodeType>) super.decode(clazz);\n  }\n\n  /**\n   * @see GlideOptions#encodeFormat(Bitmap.CompressFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideRequest<TranscodeType>) super.encodeFormat(format);\n  }\n\n  /**\n   * @see GlideOptions#encodeQuality(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideRequest<TranscodeType>) super.encodeQuality(value);\n  }\n\n  /**\n   * @see GlideOptions#frame(long)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> frame(@IntRange(from = 0) long value) {\n    return (GlideRequest<TranscodeType>) super.frame(value);\n  }\n\n  /**\n   * @see GlideOptions#format(DecodeFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> format(@NonNull DecodeFormat format) {\n    return (GlideRequest<TranscodeType>) super.format(format);\n  }\n\n  /**\n   * @see GlideOptions#disallowHardwareConfig()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> disallowHardwareConfig() {\n    return (GlideRequest<TranscodeType>) super.disallowHardwareConfig();\n  }\n\n  /**\n   * @see GlideOptions#downsample(DownsampleStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.downsample(strategy);\n  }\n\n  /**\n   * @see GlideOptions#timeout(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> timeout(@IntRange(from = 0) int value) {\n    return (GlideRequest<TranscodeType>) super.timeout(value);\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterCrop();\n  }\n\n  /**\n   * @see GlideOptions#optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalFitCenter() {\n    return (GlideRequest<TranscodeType>) super.optionalFitCenter();\n  }\n\n  /**\n   * @see GlideOptions#fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fitCenter() {\n    return (GlideRequest<TranscodeType>) super.fitCenter();\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterInside() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterInside();\n  }\n\n  /**\n   * @see GlideOptions#centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerInside() {\n    return (GlideRequest<TranscodeType>) super.centerInside();\n  }\n\n  /**\n   * @see GlideOptions#optionalCircleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCircleCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCircleCrop();\n  }\n\n  /**\n   * @see GlideOptions#circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> circleCrop() {\n    return (GlideRequest<TranscodeType>) super.circleCrop();\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>[])\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transform(transformations);\n  }\n\n  /**\n   * @see GlideOptions#transforms(Transformation<Bitmap>[])\n   */\n  @Deprecated\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transforms(\n      @NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transforms(transformations);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalTransform(\n      @NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#dontTransform()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontTransform() {\n    return (GlideRequest<TranscodeType>) super.dontTransform();\n  }\n\n  /**\n   * @see GlideOptions#dontAnimate()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontAnimate() {\n    return (GlideRequest<TranscodeType>) super.dontAnimate();\n  }\n\n  /**\n   * @see GlideOptions#lock()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> lock() {\n    return (GlideRequest<TranscodeType>) super.lock();\n  }\n\n  /**\n   * @see GlideOptions#autoClone()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> autoClone() {\n    return (GlideRequest<TranscodeType>) super.autoClone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideRequest<TranscodeType>) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> options) {\n    return (GlideRequest<TranscodeType>) super.transition(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> listener(@Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.listener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.addListener(listener);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequest<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.error(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(Object o) {\n    return (GlideRequest<TranscodeType>) super.error(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public final GlideRequest<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... builders) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builders);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable List<RequestBuilder<TranscodeType>> list) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(list);\n  }\n\n  @Override\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(float sizeMultiplier) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(sizeMultiplier);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Object o) {\n    return (GlideRequest<TranscodeType>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<TranscodeType>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable String string) {\n    return (GlideRequest<TranscodeType>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Uri uri) {\n    return (GlideRequest<TranscodeType>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable File file) {\n    return (GlideRequest<TranscodeType>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<TranscodeType>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable URL url) {\n    return (GlideRequest<TranscodeType>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable byte[] bytes) {\n    return (GlideRequest<TranscodeType>) super.load(bytes);\n  }\n\n  @Override\n  @CheckResult\n  public GlideRequest<TranscodeType> clone() {\n    return (GlideRequest<TranscodeType>) super.clone();\n  }\n\n  /**\n   * @see Extension#centerCrop(BaseRequestOptions)\n   * @see GlideRequest<TranscodeType>#centerCrop()\n   */\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  @CheckResult\n  @NonNull\n  public GlideRequest<TranscodeType> centerCrop() {\n    return (GlideRequest<TranscodeType>) Extension.centerCrop(super.centerCrop());\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideExtendMultipleArguments/Extension.java",
    "content": "package com.bumptech.glide.test;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.request.BaseRequestOptions;\n\n@GlideExtension\npublic final class Extension {\n\n  private Extension() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideOption(override = GlideOption.OVERRIDE_EXTEND)\n  public static BaseRequestOptions<?> override(BaseRequestOptions<?> requestOptions, int width, int height) {\n    return requestOptions\n        .override(width, height)\n        .centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideExtendMultipleArguments/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n * @see Extension\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform0;\n\n  private static GlideOptions centerInsideTransform1;\n\n  private static GlideOptions centerCropTransform2;\n\n  private static GlideOptions circleCropTransform3;\n\n  private static GlideOptions noTransformation4;\n\n  private static GlideOptions noAnimation5;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform0 == null) {\n      GlideOptions.fitCenterTransform0 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform0;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform1 == null) {\n      GlideOptions.centerInsideTransform1 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform2 == null) {\n      GlideOptions.centerCropTransform2 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform2;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform3 == null) {\n      GlideOptions.circleCropTransform3 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation4 == null) {\n      GlideOptions.noTransformation4 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation4;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation5 == null) {\n      GlideOptions.noAnimation5 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation5;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerCrop() {\n    return (GlideOptions) super.centerCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n\n  /**\n   * @see Extension#override(BaseRequestOptions, int, int)\n   * @see GlideOptions#override(int, int)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  @CheckResult\n  @NonNull\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) Extension.override(super.override(width, height), width, height);\n  }\n\n  /**\n   * @see Extension#override(BaseRequestOptions, int, int)\n   */\n  @CheckResult\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideExtendMultipleArguments/GlideRequest.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Contains all public methods from {@link RequestBuilder<TranscodeType>}, all options from\n * {@link com.bumptech.glide.request.RequestOptions} and all generated options from\n * {@link com.bumptech.glide.annotation.GlideOption} in annotated methods in\n * {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * <p>Generated code, do not modify.\n *\n * @see RequestBuilder<TranscodeType>\n * @see com.bumptech.glide.request.RequestOptions\n */\n@SuppressWarnings({\n    \"unused\",\n    \"deprecation\"\n})\npublic class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {\n  GlideRequest(@NonNull Class<TranscodeType> transcodeClass, @NonNull RequestBuilder<?> other) {\n    super(transcodeClass, other);\n  }\n\n  GlideRequest(@NonNull Glide glide, @NonNull RequestManager requestManager,\n      @NonNull Class<TranscodeType> transcodeClass, @NonNull Context context) {\n    super(glide, requestManager ,transcodeClass, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  protected GlideRequest<File> getDownloadOnlyRequest() {\n    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * @see GlideOptions#sizeMultiplier(float)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideRequest<TranscodeType>) super.sizeMultiplier(value);\n  }\n\n  /**\n   * @see GlideOptions#useUnlimitedSourceGeneratorsPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#useAnimationPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useAnimationPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useAnimationPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#onlyRetrieveFromCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> onlyRetrieveFromCache(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.onlyRetrieveFromCache(flag);\n  }\n\n  /**\n   * @see GlideOptions#diskCacheStrategy(DiskCacheStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see GlideOptions#priority(Priority)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> priority(@NonNull Priority priority) {\n    return (GlideRequest<TranscodeType>) super.priority(priority);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.placeholder(drawable);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.placeholder(id);\n  }\n\n  /**\n   * @see GlideOptions#fallback(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.fallback(drawable);\n  }\n\n  /**\n   * @see GlideOptions#fallback(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.fallback(id);\n  }\n\n  /**\n   * @see GlideOptions#error(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.error(drawable);\n  }\n\n  /**\n   * @see GlideOptions#error(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.error(id);\n  }\n\n  /**\n   * @see GlideOptions#theme(Resources.Theme)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> theme(@Nullable Resources.Theme theme) {\n    return (GlideRequest<TranscodeType>) super.theme(theme);\n  }\n\n  /**\n   * @see GlideOptions#skipMemoryCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> skipMemoryCache(boolean skip) {\n    return (GlideRequest<TranscodeType>) super.skipMemoryCache(skip);\n  }\n\n  /**\n   * @see GlideOptions#override(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int size) {\n    return (GlideRequest<TranscodeType>) super.override(size);\n  }\n\n  /**\n   * @see GlideOptions#signature(Key)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> signature(@NonNull Key key) {\n    return (GlideRequest<TranscodeType>) super.signature(key);\n  }\n\n  /**\n   * @see GlideOptions#set(Option<Y>, Y)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideRequest<TranscodeType>) super.set(option, y);\n  }\n\n  /**\n   * @see GlideOptions#decode(Class<?>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> decode(@NonNull Class<?> clazz) {\n    return (GlideRequest<TranscodeType>) super.decode(clazz);\n  }\n\n  /**\n   * @see GlideOptions#encodeFormat(Bitmap.CompressFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideRequest<TranscodeType>) super.encodeFormat(format);\n  }\n\n  /**\n   * @see GlideOptions#encodeQuality(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideRequest<TranscodeType>) super.encodeQuality(value);\n  }\n\n  /**\n   * @see GlideOptions#frame(long)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> frame(@IntRange(from = 0) long value) {\n    return (GlideRequest<TranscodeType>) super.frame(value);\n  }\n\n  /**\n   * @see GlideOptions#format(DecodeFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> format(@NonNull DecodeFormat format) {\n    return (GlideRequest<TranscodeType>) super.format(format);\n  }\n\n  /**\n   * @see GlideOptions#disallowHardwareConfig()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> disallowHardwareConfig() {\n    return (GlideRequest<TranscodeType>) super.disallowHardwareConfig();\n  }\n\n  /**\n   * @see GlideOptions#downsample(DownsampleStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.downsample(strategy);\n  }\n\n  /**\n   * @see GlideOptions#timeout(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> timeout(@IntRange(from = 0) int value) {\n    return (GlideRequest<TranscodeType>) super.timeout(value);\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterCrop();\n  }\n\n  /**\n   * @see GlideOptions#centerCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerCrop() {\n    return (GlideRequest<TranscodeType>) super.centerCrop();\n  }\n\n  /**\n   * @see GlideOptions#optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalFitCenter() {\n    return (GlideRequest<TranscodeType>) super.optionalFitCenter();\n  }\n\n  /**\n   * @see GlideOptions#fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fitCenter() {\n    return (GlideRequest<TranscodeType>) super.fitCenter();\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterInside() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterInside();\n  }\n\n  /**\n   * @see GlideOptions#centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerInside() {\n    return (GlideRequest<TranscodeType>) super.centerInside();\n  }\n\n  /**\n   * @see GlideOptions#optionalCircleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCircleCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCircleCrop();\n  }\n\n  /**\n   * @see GlideOptions#circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> circleCrop() {\n    return (GlideRequest<TranscodeType>) super.circleCrop();\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>[])\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transform(transformations);\n  }\n\n  /**\n   * @see GlideOptions#transforms(Transformation<Bitmap>[])\n   */\n  @Deprecated\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transforms(\n      @NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transforms(transformations);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalTransform(\n      @NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#dontTransform()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontTransform() {\n    return (GlideRequest<TranscodeType>) super.dontTransform();\n  }\n\n  /**\n   * @see GlideOptions#dontAnimate()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontAnimate() {\n    return (GlideRequest<TranscodeType>) super.dontAnimate();\n  }\n\n  /**\n   * @see GlideOptions#lock()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> lock() {\n    return (GlideRequest<TranscodeType>) super.lock();\n  }\n\n  /**\n   * @see GlideOptions#autoClone()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> autoClone() {\n    return (GlideRequest<TranscodeType>) super.autoClone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideRequest<TranscodeType>) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> options) {\n    return (GlideRequest<TranscodeType>) super.transition(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> listener(@Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.listener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.addListener(listener);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequest<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.error(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(Object o) {\n    return (GlideRequest<TranscodeType>) super.error(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public final GlideRequest<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... builders) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builders);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable List<RequestBuilder<TranscodeType>> list) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(list);\n  }\n\n  @Override\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(float sizeMultiplier) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(sizeMultiplier);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Object o) {\n    return (GlideRequest<TranscodeType>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<TranscodeType>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable String string) {\n    return (GlideRequest<TranscodeType>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Uri uri) {\n    return (GlideRequest<TranscodeType>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable File file) {\n    return (GlideRequest<TranscodeType>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<TranscodeType>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable URL url) {\n    return (GlideRequest<TranscodeType>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable byte[] bytes) {\n    return (GlideRequest<TranscodeType>) super.load(bytes);\n  }\n\n  @Override\n  @CheckResult\n  public GlideRequest<TranscodeType> clone() {\n    return (GlideRequest<TranscodeType>) super.clone();\n  }\n\n  /**\n   * @see Extension#override(BaseRequestOptions, int, int)\n   * @see GlideRequest<TranscodeType>#override(int, int)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  @CheckResult\n  @NonNull\n  public GlideRequest<TranscodeType> override(int width, int height) {\n    return (GlideRequest<TranscodeType>) Extension.override(super.override(width, height), width, height);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideReplace/Extension.java",
    "content": "package com.bumptech.glide.test;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.request.BaseRequestOptions;\n\n@GlideExtension\npublic final class Extension {\n\n  private Extension() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideOption(override = GlideOption.OVERRIDE_REPLACE)\n  public static BaseRequestOptions<?> centerCrop(BaseRequestOptions<?> requestOptions) {\n    return requestOptions.centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideReplace/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n * @see Extension\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform0;\n\n  private static GlideOptions centerInsideTransform1;\n\n  private static GlideOptions centerCropTransform2;\n\n  private static GlideOptions circleCropTransform3;\n\n  private static GlideOptions noTransformation4;\n\n  private static GlideOptions noAnimation5;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int, int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform0 == null) {\n      GlideOptions.fitCenterTransform0 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform0;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform1 == null) {\n      GlideOptions.centerInsideTransform1 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform2 == null) {\n      GlideOptions.centerCropTransform2 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform2;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform3 == null) {\n      GlideOptions.circleCropTransform3 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation4 == null) {\n      GlideOptions.noTransformation4 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation4;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation5 == null) {\n      GlideOptions.noAnimation5 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation5;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) super.override(width, height);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n\n  /**\n   * @see Extension#centerCrop(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideOptions centerCrop() {\n    return (GlideOptions) Extension.centerCrop(this);\n  }\n\n  /**\n   * @see Extension#centerCrop(BaseRequestOptions)\n   */\n  @CheckResult\n  public static GlideOptions centerCropOf() {\n    return new GlideOptions().centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/OverrideReplace/GlideRequest.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Contains all public methods from {@link RequestBuilder<TranscodeType>}, all options from\n * {@link com.bumptech.glide.request.RequestOptions} and all generated options from\n * {@link com.bumptech.glide.annotation.GlideOption} in annotated methods in\n * {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * <p>Generated code, do not modify.\n *\n * @see RequestBuilder<TranscodeType>\n * @see com.bumptech.glide.request.RequestOptions\n */\n@SuppressWarnings({\n    \"unused\",\n    \"deprecation\"\n})\npublic class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {\n  GlideRequest(@NonNull Class<TranscodeType> transcodeClass, @NonNull RequestBuilder<?> other) {\n    super(transcodeClass, other);\n  }\n\n  GlideRequest(@NonNull Glide glide, @NonNull RequestManager requestManager,\n      @NonNull Class<TranscodeType> transcodeClass, @NonNull Context context) {\n    super(glide, requestManager ,transcodeClass, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  protected GlideRequest<File> getDownloadOnlyRequest() {\n    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * @see GlideOptions#sizeMultiplier(float)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideRequest<TranscodeType>) super.sizeMultiplier(value);\n  }\n\n  /**\n   * @see GlideOptions#useUnlimitedSourceGeneratorsPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#useAnimationPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useAnimationPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useAnimationPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#onlyRetrieveFromCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> onlyRetrieveFromCache(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.onlyRetrieveFromCache(flag);\n  }\n\n  /**\n   * @see GlideOptions#diskCacheStrategy(DiskCacheStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see GlideOptions#priority(Priority)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> priority(@NonNull Priority priority) {\n    return (GlideRequest<TranscodeType>) super.priority(priority);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.placeholder(drawable);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.placeholder(id);\n  }\n\n  /**\n   * @see GlideOptions#fallback(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.fallback(drawable);\n  }\n\n  /**\n   * @see GlideOptions#fallback(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.fallback(id);\n  }\n\n  /**\n   * @see GlideOptions#error(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.error(drawable);\n  }\n\n  /**\n   * @see GlideOptions#error(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.error(id);\n  }\n\n  /**\n   * @see GlideOptions#theme(Resources.Theme)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> theme(@Nullable Resources.Theme theme) {\n    return (GlideRequest<TranscodeType>) super.theme(theme);\n  }\n\n  /**\n   * @see GlideOptions#skipMemoryCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> skipMemoryCache(boolean skip) {\n    return (GlideRequest<TranscodeType>) super.skipMemoryCache(skip);\n  }\n\n  /**\n   * @see GlideOptions#override(int, int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int width, int height) {\n    return (GlideRequest<TranscodeType>) super.override(width, height);\n  }\n\n  /**\n   * @see GlideOptions#override(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int size) {\n    return (GlideRequest<TranscodeType>) super.override(size);\n  }\n\n  /**\n   * @see GlideOptions#signature(Key)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> signature(@NonNull Key key) {\n    return (GlideRequest<TranscodeType>) super.signature(key);\n  }\n\n  /**\n   * @see GlideOptions#set(Option<Y>, Y)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideRequest<TranscodeType>) super.set(option, y);\n  }\n\n  /**\n   * @see GlideOptions#decode(Class<?>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> decode(@NonNull Class<?> clazz) {\n    return (GlideRequest<TranscodeType>) super.decode(clazz);\n  }\n\n  /**\n   * @see GlideOptions#encodeFormat(Bitmap.CompressFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideRequest<TranscodeType>) super.encodeFormat(format);\n  }\n\n  /**\n   * @see GlideOptions#encodeQuality(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideRequest<TranscodeType>) super.encodeQuality(value);\n  }\n\n  /**\n   * @see GlideOptions#frame(long)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> frame(@IntRange(from = 0) long value) {\n    return (GlideRequest<TranscodeType>) super.frame(value);\n  }\n\n  /**\n   * @see GlideOptions#format(DecodeFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> format(@NonNull DecodeFormat format) {\n    return (GlideRequest<TranscodeType>) super.format(format);\n  }\n\n  /**\n   * @see GlideOptions#disallowHardwareConfig()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> disallowHardwareConfig() {\n    return (GlideRequest<TranscodeType>) super.disallowHardwareConfig();\n  }\n\n  /**\n   * @see GlideOptions#downsample(DownsampleStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.downsample(strategy);\n  }\n\n  /**\n   * @see GlideOptions#timeout(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> timeout(@IntRange(from = 0) int value) {\n    return (GlideRequest<TranscodeType>) super.timeout(value);\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterCrop();\n  }\n\n  /**\n   * @see GlideOptions#optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalFitCenter() {\n    return (GlideRequest<TranscodeType>) super.optionalFitCenter();\n  }\n\n  /**\n   * @see GlideOptions#fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fitCenter() {\n    return (GlideRequest<TranscodeType>) super.fitCenter();\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterInside() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterInside();\n  }\n\n  /**\n   * @see GlideOptions#centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerInside() {\n    return (GlideRequest<TranscodeType>) super.centerInside();\n  }\n\n  /**\n   * @see GlideOptions#optionalCircleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCircleCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCircleCrop();\n  }\n\n  /**\n   * @see GlideOptions#circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> circleCrop() {\n    return (GlideRequest<TranscodeType>) super.circleCrop();\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>[])\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transform(transformations);\n  }\n\n  /**\n   * @see GlideOptions#transforms(Transformation<Bitmap>[])\n   */\n  @Deprecated\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transforms(\n      @NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transforms(transformations);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalTransform(\n      @NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#dontTransform()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontTransform() {\n    return (GlideRequest<TranscodeType>) super.dontTransform();\n  }\n\n  /**\n   * @see GlideOptions#dontAnimate()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontAnimate() {\n    return (GlideRequest<TranscodeType>) super.dontAnimate();\n  }\n\n  /**\n   * @see GlideOptions#lock()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> lock() {\n    return (GlideRequest<TranscodeType>) super.lock();\n  }\n\n  /**\n   * @see GlideOptions#autoClone()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> autoClone() {\n    return (GlideRequest<TranscodeType>) super.autoClone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideRequest<TranscodeType>) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> options) {\n    return (GlideRequest<TranscodeType>) super.transition(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> listener(@Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.listener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.addListener(listener);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequest<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.error(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(Object o) {\n    return (GlideRequest<TranscodeType>) super.error(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public final GlideRequest<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... builders) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builders);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable List<RequestBuilder<TranscodeType>> list) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(list);\n  }\n\n  @Override\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(float sizeMultiplier) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(sizeMultiplier);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Object o) {\n    return (GlideRequest<TranscodeType>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<TranscodeType>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable String string) {\n    return (GlideRequest<TranscodeType>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Uri uri) {\n    return (GlideRequest<TranscodeType>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable File file) {\n    return (GlideRequest<TranscodeType>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<TranscodeType>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable URL url) {\n    return (GlideRequest<TranscodeType>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable byte[] bytes) {\n    return (GlideRequest<TranscodeType>) super.load(bytes);\n  }\n\n  @Override\n  @CheckResult\n  public GlideRequest<TranscodeType> clone() {\n    return (GlideRequest<TranscodeType>) super.clone();\n  }\n\n  /**\n   * @see Extension#centerCrop(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideRequest<TranscodeType> centerCrop() {\n    return (GlideRequest<TranscodeType>) Extension.centerCrop(this);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/SkipStaticMethod/Extension.java",
    "content": "package com.bumptech.glide.test;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.request.BaseRequestOptions;\n\n@GlideExtension\npublic final class Extension {\n\n  private Extension() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideOption(skipStaticMethod = true)\n  public static BaseRequestOptions<?> test(BaseRequestOptions<?> requestOptions) {\n    return requestOptions.centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/SkipStaticMethod/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n * @see Extension\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform0;\n\n  private static GlideOptions centerInsideTransform1;\n\n  private static GlideOptions centerCropTransform2;\n\n  private static GlideOptions circleCropTransform3;\n\n  private static GlideOptions noTransformation4;\n\n  private static GlideOptions noAnimation5;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int, int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform0 == null) {\n      GlideOptions.fitCenterTransform0 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform0;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform1 == null) {\n      GlideOptions.centerInsideTransform1 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform2 == null) {\n      GlideOptions.centerCropTransform2 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform2;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform3 == null) {\n      GlideOptions.circleCropTransform3 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation4 == null) {\n      GlideOptions.noTransformation4 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation4;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation5 == null) {\n      GlideOptions.noAnimation5 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation5;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) super.override(width, height);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerCrop() {\n    return (GlideOptions) super.centerCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n\n  /**\n   * @see Extension#test(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideOptions test() {\n    return (GlideOptions) Extension.test(this);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/SkipStaticMethod/GlideRequest.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Contains all public methods from {@link RequestBuilder<TranscodeType>}, all options from\n * {@link com.bumptech.glide.request.RequestOptions} and all generated options from\n * {@link com.bumptech.glide.annotation.GlideOption} in annotated methods in\n * {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * <p>Generated code, do not modify.\n *\n * @see RequestBuilder<TranscodeType>\n * @see com.bumptech.glide.request.RequestOptions\n */\n@SuppressWarnings({\n    \"unused\",\n    \"deprecation\"\n})\npublic class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {\n  GlideRequest(@NonNull Class<TranscodeType> transcodeClass, @NonNull RequestBuilder<?> other) {\n    super(transcodeClass, other);\n  }\n\n  GlideRequest(@NonNull Glide glide, @NonNull RequestManager requestManager,\n      @NonNull Class<TranscodeType> transcodeClass, @NonNull Context context) {\n    super(glide, requestManager ,transcodeClass, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  protected GlideRequest<File> getDownloadOnlyRequest() {\n    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * @see GlideOptions#sizeMultiplier(float)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideRequest<TranscodeType>) super.sizeMultiplier(value);\n  }\n\n  /**\n   * @see GlideOptions#useUnlimitedSourceGeneratorsPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#useAnimationPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useAnimationPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useAnimationPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#onlyRetrieveFromCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> onlyRetrieveFromCache(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.onlyRetrieveFromCache(flag);\n  }\n\n  /**\n   * @see GlideOptions#diskCacheStrategy(DiskCacheStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see GlideOptions#priority(Priority)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> priority(@NonNull Priority priority) {\n    return (GlideRequest<TranscodeType>) super.priority(priority);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.placeholder(drawable);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.placeholder(id);\n  }\n\n  /**\n   * @see GlideOptions#fallback(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.fallback(drawable);\n  }\n\n  /**\n   * @see GlideOptions#fallback(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.fallback(id);\n  }\n\n  /**\n   * @see GlideOptions#error(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.error(drawable);\n  }\n\n  /**\n   * @see GlideOptions#error(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.error(id);\n  }\n\n  /**\n   * @see GlideOptions#theme(Resources.Theme)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> theme(@Nullable Resources.Theme theme) {\n    return (GlideRequest<TranscodeType>) super.theme(theme);\n  }\n\n  /**\n   * @see GlideOptions#skipMemoryCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> skipMemoryCache(boolean skip) {\n    return (GlideRequest<TranscodeType>) super.skipMemoryCache(skip);\n  }\n\n  /**\n   * @see GlideOptions#override(int, int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int width, int height) {\n    return (GlideRequest<TranscodeType>) super.override(width, height);\n  }\n\n  /**\n   * @see GlideOptions#override(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int size) {\n    return (GlideRequest<TranscodeType>) super.override(size);\n  }\n\n  /**\n   * @see GlideOptions#signature(Key)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> signature(@NonNull Key key) {\n    return (GlideRequest<TranscodeType>) super.signature(key);\n  }\n\n  /**\n   * @see GlideOptions#set(Option<Y>, Y)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideRequest<TranscodeType>) super.set(option, y);\n  }\n\n  /**\n   * @see GlideOptions#decode(Class<?>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> decode(@NonNull Class<?> clazz) {\n    return (GlideRequest<TranscodeType>) super.decode(clazz);\n  }\n\n  /**\n   * @see GlideOptions#encodeFormat(Bitmap.CompressFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideRequest<TranscodeType>) super.encodeFormat(format);\n  }\n\n  /**\n   * @see GlideOptions#encodeQuality(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideRequest<TranscodeType>) super.encodeQuality(value);\n  }\n\n  /**\n   * @see GlideOptions#frame(long)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> frame(@IntRange(from = 0) long value) {\n    return (GlideRequest<TranscodeType>) super.frame(value);\n  }\n\n  /**\n   * @see GlideOptions#format(DecodeFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> format(@NonNull DecodeFormat format) {\n    return (GlideRequest<TranscodeType>) super.format(format);\n  }\n\n  /**\n   * @see GlideOptions#disallowHardwareConfig()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> disallowHardwareConfig() {\n    return (GlideRequest<TranscodeType>) super.disallowHardwareConfig();\n  }\n\n  /**\n   * @see GlideOptions#downsample(DownsampleStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.downsample(strategy);\n  }\n\n  /**\n   * @see GlideOptions#timeout(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> timeout(@IntRange(from = 0) int value) {\n    return (GlideRequest<TranscodeType>) super.timeout(value);\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterCrop();\n  }\n\n  /**\n   * @see GlideOptions#centerCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerCrop() {\n    return (GlideRequest<TranscodeType>) super.centerCrop();\n  }\n\n  /**\n   * @see GlideOptions#optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalFitCenter() {\n    return (GlideRequest<TranscodeType>) super.optionalFitCenter();\n  }\n\n  /**\n   * @see GlideOptions#fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fitCenter() {\n    return (GlideRequest<TranscodeType>) super.fitCenter();\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterInside() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterInside();\n  }\n\n  /**\n   * @see GlideOptions#centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerInside() {\n    return (GlideRequest<TranscodeType>) super.centerInside();\n  }\n\n  /**\n   * @see GlideOptions#optionalCircleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCircleCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCircleCrop();\n  }\n\n  /**\n   * @see GlideOptions#circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> circleCrop() {\n    return (GlideRequest<TranscodeType>) super.circleCrop();\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>[])\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transform(transformations);\n  }\n\n  /**\n   * @see GlideOptions#transforms(Transformation<Bitmap>[])\n   */\n  @Deprecated\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transforms(\n      @NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transforms(transformations);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalTransform(\n      @NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#dontTransform()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontTransform() {\n    return (GlideRequest<TranscodeType>) super.dontTransform();\n  }\n\n  /**\n   * @see GlideOptions#dontAnimate()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontAnimate() {\n    return (GlideRequest<TranscodeType>) super.dontAnimate();\n  }\n\n  /**\n   * @see GlideOptions#lock()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> lock() {\n    return (GlideRequest<TranscodeType>) super.lock();\n  }\n\n  /**\n   * @see GlideOptions#autoClone()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> autoClone() {\n    return (GlideRequest<TranscodeType>) super.autoClone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideRequest<TranscodeType>) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> options) {\n    return (GlideRequest<TranscodeType>) super.transition(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> listener(@Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.listener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.addListener(listener);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequest<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.error(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(Object o) {\n    return (GlideRequest<TranscodeType>) super.error(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public final GlideRequest<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... builders) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builders);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable List<RequestBuilder<TranscodeType>> list) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(list);\n  }\n\n  @Override\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(float sizeMultiplier) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(sizeMultiplier);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Object o) {\n    return (GlideRequest<TranscodeType>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<TranscodeType>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable String string) {\n    return (GlideRequest<TranscodeType>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Uri uri) {\n    return (GlideRequest<TranscodeType>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable File file) {\n    return (GlideRequest<TranscodeType>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<TranscodeType>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable URL url) {\n    return (GlideRequest<TranscodeType>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable byte[] bytes) {\n    return (GlideRequest<TranscodeType>) super.load(bytes);\n  }\n\n  @Override\n  @CheckResult\n  public GlideRequest<TranscodeType> clone() {\n    return (GlideRequest<TranscodeType>) super.clone();\n  }\n\n  /**\n   * @see Extension#test(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideRequest<TranscodeType> test() {\n    return (GlideRequest<TranscodeType>) Extension.test(this);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/StaticMethodName/Extension.java",
    "content": "package com.bumptech.glide.test;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.request.BaseRequestOptions;\n\n@GlideExtension\npublic final class Extension {\n\n  private Extension() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideOption(staticMethodName = \"testSomething\")\n  public static BaseRequestOptions<?> test(BaseRequestOptions<?> requestOptions) {\n    return requestOptions.centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/StaticMethodName/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n * @see Extension\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform0;\n\n  private static GlideOptions centerInsideTransform1;\n\n  private static GlideOptions centerCropTransform2;\n\n  private static GlideOptions circleCropTransform3;\n\n  private static GlideOptions noTransformation4;\n\n  private static GlideOptions noAnimation5;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int, int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform0 == null) {\n      GlideOptions.fitCenterTransform0 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform0;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform1 == null) {\n      GlideOptions.centerInsideTransform1 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform2 == null) {\n      GlideOptions.centerCropTransform2 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform2;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform3 == null) {\n      GlideOptions.circleCropTransform3 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation4 == null) {\n      GlideOptions.noTransformation4 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation4;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation5 == null) {\n      GlideOptions.noAnimation5 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation5;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) super.override(width, height);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerCrop() {\n    return (GlideOptions) super.centerCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n\n  /**\n   * @see Extension#test(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideOptions test() {\n    return (GlideOptions) Extension.test(this);\n  }\n\n  /**\n   * @see Extension#test(BaseRequestOptions)\n   */\n  @CheckResult\n  public static GlideOptions testSomething() {\n    return new GlideOptions().test();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionOptionsTest/StaticMethodName/GlideRequest.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Contains all public methods from {@link RequestBuilder<TranscodeType>}, all options from\n * {@link com.bumptech.glide.request.RequestOptions} and all generated options from\n * {@link com.bumptech.glide.annotation.GlideOption} in annotated methods in\n * {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * <p>Generated code, do not modify.\n *\n * @see RequestBuilder<TranscodeType>\n * @see com.bumptech.glide.request.RequestOptions\n */\n@SuppressWarnings({\n    \"unused\",\n    \"deprecation\"\n})\npublic class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {\n  GlideRequest(@NonNull Class<TranscodeType> transcodeClass, @NonNull RequestBuilder<?> other) {\n    super(transcodeClass, other);\n  }\n\n  GlideRequest(@NonNull Glide glide, @NonNull RequestManager requestManager,\n      @NonNull Class<TranscodeType> transcodeClass, @NonNull Context context) {\n    super(glide, requestManager ,transcodeClass, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  protected GlideRequest<File> getDownloadOnlyRequest() {\n    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * @see GlideOptions#sizeMultiplier(float)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideRequest<TranscodeType>) super.sizeMultiplier(value);\n  }\n\n  /**\n   * @see GlideOptions#useUnlimitedSourceGeneratorsPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#useAnimationPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useAnimationPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useAnimationPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#onlyRetrieveFromCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> onlyRetrieveFromCache(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.onlyRetrieveFromCache(flag);\n  }\n\n  /**\n   * @see GlideOptions#diskCacheStrategy(DiskCacheStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see GlideOptions#priority(Priority)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> priority(@NonNull Priority priority) {\n    return (GlideRequest<TranscodeType>) super.priority(priority);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.placeholder(drawable);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.placeholder(id);\n  }\n\n  /**\n   * @see GlideOptions#fallback(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.fallback(drawable);\n  }\n\n  /**\n   * @see GlideOptions#fallback(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.fallback(id);\n  }\n\n  /**\n   * @see GlideOptions#error(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.error(drawable);\n  }\n\n  /**\n   * @see GlideOptions#error(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.error(id);\n  }\n\n  /**\n   * @see GlideOptions#theme(Resources.Theme)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> theme(@Nullable Resources.Theme theme) {\n    return (GlideRequest<TranscodeType>) super.theme(theme);\n  }\n\n  /**\n   * @see GlideOptions#skipMemoryCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> skipMemoryCache(boolean skip) {\n    return (GlideRequest<TranscodeType>) super.skipMemoryCache(skip);\n  }\n\n  /**\n   * @see GlideOptions#override(int, int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int width, int height) {\n    return (GlideRequest<TranscodeType>) super.override(width, height);\n  }\n\n  /**\n   * @see GlideOptions#override(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int size) {\n    return (GlideRequest<TranscodeType>) super.override(size);\n  }\n\n  /**\n   * @see GlideOptions#signature(Key)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> signature(@NonNull Key key) {\n    return (GlideRequest<TranscodeType>) super.signature(key);\n  }\n\n  /**\n   * @see GlideOptions#set(Option<Y>, Y)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideRequest<TranscodeType>) super.set(option, y);\n  }\n\n  /**\n   * @see GlideOptions#decode(Class<?>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> decode(@NonNull Class<?> clazz) {\n    return (GlideRequest<TranscodeType>) super.decode(clazz);\n  }\n\n  /**\n   * @see GlideOptions#encodeFormat(Bitmap.CompressFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideRequest<TranscodeType>) super.encodeFormat(format);\n  }\n\n  /**\n   * @see GlideOptions#encodeQuality(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideRequest<TranscodeType>) super.encodeQuality(value);\n  }\n\n  /**\n   * @see GlideOptions#frame(long)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> frame(@IntRange(from = 0) long value) {\n    return (GlideRequest<TranscodeType>) super.frame(value);\n  }\n\n  /**\n   * @see GlideOptions#format(DecodeFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> format(@NonNull DecodeFormat format) {\n    return (GlideRequest<TranscodeType>) super.format(format);\n  }\n\n  /**\n   * @see GlideOptions#disallowHardwareConfig()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> disallowHardwareConfig() {\n    return (GlideRequest<TranscodeType>) super.disallowHardwareConfig();\n  }\n\n  /**\n   * @see GlideOptions#downsample(DownsampleStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.downsample(strategy);\n  }\n\n  /**\n   * @see GlideOptions#timeout(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> timeout(@IntRange(from = 0) int value) {\n    return (GlideRequest<TranscodeType>) super.timeout(value);\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterCrop();\n  }\n\n  /**\n   * @see GlideOptions#centerCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerCrop() {\n    return (GlideRequest<TranscodeType>) super.centerCrop();\n  }\n\n  /**\n   * @see GlideOptions#optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalFitCenter() {\n    return (GlideRequest<TranscodeType>) super.optionalFitCenter();\n  }\n\n  /**\n   * @see GlideOptions#fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fitCenter() {\n    return (GlideRequest<TranscodeType>) super.fitCenter();\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterInside() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterInside();\n  }\n\n  /**\n   * @see GlideOptions#centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerInside() {\n    return (GlideRequest<TranscodeType>) super.centerInside();\n  }\n\n  /**\n   * @see GlideOptions#optionalCircleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCircleCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCircleCrop();\n  }\n\n  /**\n   * @see GlideOptions#circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> circleCrop() {\n    return (GlideRequest<TranscodeType>) super.circleCrop();\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>[])\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transform(transformations);\n  }\n\n  /**\n   * @see GlideOptions#transforms(Transformation<Bitmap>[])\n   */\n  @Deprecated\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transforms(\n      @NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transforms(transformations);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalTransform(\n      @NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#dontTransform()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontTransform() {\n    return (GlideRequest<TranscodeType>) super.dontTransform();\n  }\n\n  /**\n   * @see GlideOptions#dontAnimate()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontAnimate() {\n    return (GlideRequest<TranscodeType>) super.dontAnimate();\n  }\n\n  /**\n   * @see GlideOptions#lock()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> lock() {\n    return (GlideRequest<TranscodeType>) super.lock();\n  }\n\n  /**\n   * @see GlideOptions#autoClone()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> autoClone() {\n    return (GlideRequest<TranscodeType>) super.autoClone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideRequest<TranscodeType>) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> options) {\n    return (GlideRequest<TranscodeType>) super.transition(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> listener(@Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.listener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.addListener(listener);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequest<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.error(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(Object o) {\n    return (GlideRequest<TranscodeType>) super.error(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public final GlideRequest<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... builders) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builders);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable List<RequestBuilder<TranscodeType>> list) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(list);\n  }\n\n  @Override\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(float sizeMultiplier) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(sizeMultiplier);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Object o) {\n    return (GlideRequest<TranscodeType>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<TranscodeType>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable String string) {\n    return (GlideRequest<TranscodeType>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Uri uri) {\n    return (GlideRequest<TranscodeType>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable File file) {\n    return (GlideRequest<TranscodeType>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<TranscodeType>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable URL url) {\n    return (GlideRequest<TranscodeType>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable byte[] bytes) {\n    return (GlideRequest<TranscodeType>) super.load(bytes);\n  }\n\n  @Override\n  @CheckResult\n  public GlideRequest<TranscodeType> clone() {\n    return (GlideRequest<TranscodeType>) super.clone();\n  }\n\n  /**\n   * @see Extension#test(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideRequest<TranscodeType> test() {\n    return (GlideRequest<TranscodeType>) Extension.test(this);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionWithOptionTest/ExtensionWithOption.java",
    "content": "package com.bumptech.glide.test;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.request.BaseRequestOptions;\n\n@GlideExtension\npublic final class ExtensionWithOption {\n\n  private ExtensionWithOption() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideOption\n  public static BaseRequestOptions<?> squareThumb(BaseRequestOptions<?> requestOptions) {\n    return requestOptions.centerCrop();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionWithOptionTest/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n * @see ExtensionWithOption\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform0;\n\n  private static GlideOptions centerInsideTransform1;\n\n  private static GlideOptions centerCropTransform2;\n\n  private static GlideOptions circleCropTransform3;\n\n  private static GlideOptions noTransformation4;\n\n  private static GlideOptions noAnimation5;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int, int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform0 == null) {\n      GlideOptions.fitCenterTransform0 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform0;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform1 == null) {\n      GlideOptions.centerInsideTransform1 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform2 == null) {\n      GlideOptions.centerCropTransform2 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform2;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform3 == null) {\n      GlideOptions.circleCropTransform3 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation4 == null) {\n      GlideOptions.noTransformation4 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation4;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation5 == null) {\n      GlideOptions.noAnimation5 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation5;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) super.override(width, height);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerCrop() {\n    return (GlideOptions) super.centerCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n\n  /**\n   * @see ExtensionWithOption#squareThumb(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideOptions squareThumb() {\n    return (GlideOptions) ExtensionWithOption.squareThumb(this);\n  }\n\n  /**\n   * @see ExtensionWithOption#squareThumb(BaseRequestOptions)\n   */\n  @CheckResult\n  public static GlideOptions squareThumbOf() {\n    return new GlideOptions().squareThumb();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionWithOptionTest/GlideRequest.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Contains all public methods from {@link RequestBuilder<TranscodeType>}, all options from\n * {@link com.bumptech.glide.request.RequestOptions} and all generated options from\n * {@link com.bumptech.glide.annotation.GlideOption} in annotated methods in\n * {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * <p>Generated code, do not modify.\n *\n * @see RequestBuilder<TranscodeType>\n * @see com.bumptech.glide.request.RequestOptions\n */\n@SuppressWarnings({\n    \"unused\",\n    \"deprecation\"\n})\npublic class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {\n  GlideRequest(@NonNull Class<TranscodeType> transcodeClass, @NonNull RequestBuilder<?> other) {\n    super(transcodeClass, other);\n  }\n\n  GlideRequest(@NonNull Glide glide, @NonNull RequestManager requestManager,\n      @NonNull Class<TranscodeType> transcodeClass, @NonNull Context context) {\n    super(glide, requestManager ,transcodeClass, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  protected GlideRequest<File> getDownloadOnlyRequest() {\n    return new GlideRequest<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * @see GlideOptions#sizeMultiplier(float)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideRequest<TranscodeType>) super.sizeMultiplier(value);\n  }\n\n  /**\n   * @see GlideOptions#useUnlimitedSourceGeneratorsPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#useAnimationPool(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> useAnimationPool(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.useAnimationPool(flag);\n  }\n\n  /**\n   * @see GlideOptions#onlyRetrieveFromCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> onlyRetrieveFromCache(boolean flag) {\n    return (GlideRequest<TranscodeType>) super.onlyRetrieveFromCache(flag);\n  }\n\n  /**\n   * @see GlideOptions#diskCacheStrategy(DiskCacheStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see GlideOptions#priority(Priority)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> priority(@NonNull Priority priority) {\n    return (GlideRequest<TranscodeType>) super.priority(priority);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.placeholder(drawable);\n  }\n\n  /**\n   * @see GlideOptions#placeholder(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> placeholder(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.placeholder(id);\n  }\n\n  /**\n   * @see GlideOptions#fallback(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.fallback(drawable);\n  }\n\n  /**\n   * @see GlideOptions#fallback(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fallback(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.fallback(id);\n  }\n\n  /**\n   * @see GlideOptions#error(Drawable)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.error(drawable);\n  }\n\n  /**\n   * @see GlideOptions#error(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(@DrawableRes int id) {\n    return (GlideRequest<TranscodeType>) super.error(id);\n  }\n\n  /**\n   * @see GlideOptions#theme(Resources.Theme)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> theme(@Nullable Resources.Theme theme) {\n    return (GlideRequest<TranscodeType>) super.theme(theme);\n  }\n\n  /**\n   * @see GlideOptions#skipMemoryCache(boolean)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> skipMemoryCache(boolean skip) {\n    return (GlideRequest<TranscodeType>) super.skipMemoryCache(skip);\n  }\n\n  /**\n   * @see GlideOptions#override(int, int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int width, int height) {\n    return (GlideRequest<TranscodeType>) super.override(width, height);\n  }\n\n  /**\n   * @see GlideOptions#override(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> override(int size) {\n    return (GlideRequest<TranscodeType>) super.override(size);\n  }\n\n  /**\n   * @see GlideOptions#signature(Key)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> signature(@NonNull Key key) {\n    return (GlideRequest<TranscodeType>) super.signature(key);\n  }\n\n  /**\n   * @see GlideOptions#set(Option<Y>, Y)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideRequest<TranscodeType>) super.set(option, y);\n  }\n\n  /**\n   * @see GlideOptions#decode(Class<?>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> decode(@NonNull Class<?> clazz) {\n    return (GlideRequest<TranscodeType>) super.decode(clazz);\n  }\n\n  /**\n   * @see GlideOptions#encodeFormat(Bitmap.CompressFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideRequest<TranscodeType>) super.encodeFormat(format);\n  }\n\n  /**\n   * @see GlideOptions#encodeQuality(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideRequest<TranscodeType>) super.encodeQuality(value);\n  }\n\n  /**\n   * @see GlideOptions#frame(long)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> frame(@IntRange(from = 0) long value) {\n    return (GlideRequest<TranscodeType>) super.frame(value);\n  }\n\n  /**\n   * @see GlideOptions#format(DecodeFormat)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> format(@NonNull DecodeFormat format) {\n    return (GlideRequest<TranscodeType>) super.format(format);\n  }\n\n  /**\n   * @see GlideOptions#disallowHardwareConfig()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> disallowHardwareConfig() {\n    return (GlideRequest<TranscodeType>) super.disallowHardwareConfig();\n  }\n\n  /**\n   * @see GlideOptions#downsample(DownsampleStrategy)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideRequest<TranscodeType>) super.downsample(strategy);\n  }\n\n  /**\n   * @see GlideOptions#timeout(int)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> timeout(@IntRange(from = 0) int value) {\n    return (GlideRequest<TranscodeType>) super.timeout(value);\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterCrop();\n  }\n\n  /**\n   * @see GlideOptions#centerCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerCrop() {\n    return (GlideRequest<TranscodeType>) super.centerCrop();\n  }\n\n  /**\n   * @see GlideOptions#optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalFitCenter() {\n    return (GlideRequest<TranscodeType>) super.optionalFitCenter();\n  }\n\n  /**\n   * @see GlideOptions#fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> fitCenter() {\n    return (GlideRequest<TranscodeType>) super.fitCenter();\n  }\n\n  /**\n   * @see GlideOptions#optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCenterInside() {\n    return (GlideRequest<TranscodeType>) super.optionalCenterInside();\n  }\n\n  /**\n   * @see GlideOptions#centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> centerInside() {\n    return (GlideRequest<TranscodeType>) super.centerInside();\n  }\n\n  /**\n   * @see GlideOptions#optionalCircleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalCircleCrop() {\n    return (GlideRequest<TranscodeType>) super.optionalCircleCrop();\n  }\n\n  /**\n   * @see GlideOptions#circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> circleCrop() {\n    return (GlideRequest<TranscodeType>) super.circleCrop();\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Transformation<Bitmap>[])\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transform(transformations);\n  }\n\n  /**\n   * @see GlideOptions#transforms(Transformation<Bitmap>[])\n   */\n  @Deprecated\n  @NonNull\n  @CheckResult\n  @SuppressWarnings({\n      \"unchecked\",\n      \"varargs\"\n  })\n  public GlideRequest<TranscodeType> transforms(\n      @NonNull Transformation<Bitmap>... transformations) {\n    return (GlideRequest<TranscodeType>) super.transforms(transformations);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Transformation<Bitmap>)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> optionalTransform(\n      @NonNull Transformation<Bitmap> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(transformation);\n  }\n\n  /**\n   * @see GlideOptions#optionalTransform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.optionalTransform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#transform(Class<Y>, Transformation<Y>)\n   */\n  @NonNull\n  @CheckResult\n  public <Y> GlideRequest<TranscodeType> transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideRequest<TranscodeType>) super.transform(clazz, transformation);\n  }\n\n  /**\n   * @see GlideOptions#dontTransform()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontTransform() {\n    return (GlideRequest<TranscodeType>) super.dontTransform();\n  }\n\n  /**\n   * @see GlideOptions#dontAnimate()\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> dontAnimate() {\n    return (GlideRequest<TranscodeType>) super.dontAnimate();\n  }\n\n  /**\n   * @see GlideOptions#lock()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> lock() {\n    return (GlideRequest<TranscodeType>) super.lock();\n  }\n\n  /**\n   * @see GlideOptions#autoClone()\n   */\n  @NonNull\n  public GlideRequest<TranscodeType> autoClone() {\n    return (GlideRequest<TranscodeType>) super.autoClone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideRequest<TranscodeType>) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> options) {\n    return (GlideRequest<TranscodeType>) super.transition(options);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> listener(@Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.listener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> listener) {\n    return (GlideRequest<TranscodeType>) super.addListener(listener);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequest<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.error(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> error(Object o) {\n    return (GlideRequest<TranscodeType>) super.error(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable RequestBuilder<TranscodeType> builder) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builder);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public final GlideRequest<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... builders) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(builders);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(@Nullable List<RequestBuilder<TranscodeType>> list) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(list);\n  }\n\n  @Override\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> thumbnail(float sizeMultiplier) {\n    return (GlideRequest<TranscodeType>) super.thumbnail(sizeMultiplier);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Object o) {\n    return (GlideRequest<TranscodeType>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<TranscodeType>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Drawable drawable) {\n    return (GlideRequest<TranscodeType>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable String string) {\n    return (GlideRequest<TranscodeType>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable Uri uri) {\n    return (GlideRequest<TranscodeType>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable File file) {\n    return (GlideRequest<TranscodeType>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<TranscodeType>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable URL url) {\n    return (GlideRequest<TranscodeType>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<TranscodeType> load(@Nullable byte[] bytes) {\n    return (GlideRequest<TranscodeType>) super.load(bytes);\n  }\n\n  @Override\n  @CheckResult\n  public GlideRequest<TranscodeType> clone() {\n    return (GlideRequest<TranscodeType>) super.clone();\n  }\n\n  /**\n   * @see ExtensionWithOption#squareThumb(BaseRequestOptions)\n   */\n  @SuppressWarnings(\"unchecked\")\n  @CheckResult\n  @NonNull\n  public GlideRequest<TranscodeType> squareThumb() {\n    return (GlideRequest<TranscodeType>) ExtensionWithOption.squareThumb(this);\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionWithTypeTest/ExtensionWithType.java",
    "content": "package com.bumptech.glide.test;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideType;\n\n@GlideExtension\npublic final class ExtensionWithType {\n\n  private ExtensionWithType() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideType(Number.class)\n  public static RequestBuilder<Number> asNumber(RequestBuilder<Number> builder) {\n    return builder;\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionWithTypeTest/GlideOptions.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Automatically generated from {@link com.bumptech.glide.annotation.GlideExtension} annotated classes.\n *\n * @see RequestOptions\n * @see ExtensionWithType\n */\n@SuppressWarnings(\"deprecation\")\npublic final class GlideOptions extends RequestOptions implements Cloneable {\n  private static GlideOptions fitCenterTransform0;\n\n  private static GlideOptions centerInsideTransform1;\n\n  private static GlideOptions centerCropTransform2;\n\n  private static GlideOptions circleCropTransform3;\n\n  private static GlideOptions noTransformation4;\n\n  private static GlideOptions noAnimation5;\n\n  /**\n   * @see RequestOptions#sizeMultiplierOf(float)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions sizeMultiplierOf(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return new GlideOptions().sizeMultiplier(value);\n  }\n\n  /**\n   * @see RequestOptions#diskCacheStrategyOf(DiskCacheStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy strategy) {\n    return new GlideOptions().diskCacheStrategy(strategy);\n  }\n\n  /**\n   * @see RequestOptions#priorityOf(Priority)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions priorityOf(@NonNull Priority priority) {\n    return new GlideOptions().priority(priority);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@Nullable Drawable drawable) {\n    return new GlideOptions().placeholder(drawable);\n  }\n\n  /**\n   * @see RequestOptions#placeholderOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions placeholderOf(@DrawableRes int id) {\n    return new GlideOptions().placeholder(id);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(Drawable)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@Nullable Drawable drawable) {\n    return new GlideOptions().error(drawable);\n  }\n\n  /**\n   * @see RequestOptions#errorOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions errorOf(@DrawableRes int id) {\n    return new GlideOptions().error(id);\n  }\n\n  /**\n   * @see RequestOptions#skipMemoryCacheOf(boolean)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    return new GlideOptions().skipMemoryCache(skipMemoryCache);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int, int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int width, int height) {\n    return new GlideOptions().override(width, height);\n  }\n\n  /**\n   * @see RequestOptions#overrideOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions overrideOf(int size) {\n    return new GlideOptions().override(size);\n  }\n\n  /**\n   * @see RequestOptions#signatureOf(Key)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions signatureOf(@NonNull Key key) {\n    return new GlideOptions().signature(key);\n  }\n\n  /**\n   * @see RequestOptions#fitCenterTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions fitCenterTransform() {\n    if (GlideOptions.fitCenterTransform0 == null) {\n      GlideOptions.fitCenterTransform0 =\n          new GlideOptions().fitCenter().autoClone();\n    }\n    return GlideOptions.fitCenterTransform0;\n  }\n\n  /**\n   * @see RequestOptions#centerInsideTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerInsideTransform() {\n    if (GlideOptions.centerInsideTransform1 == null) {\n      GlideOptions.centerInsideTransform1 =\n          new GlideOptions().centerInside().autoClone();\n    }\n    return GlideOptions.centerInsideTransform1;\n  }\n\n  /**\n   * @see RequestOptions#centerCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions centerCropTransform() {\n    if (GlideOptions.centerCropTransform2 == null) {\n      GlideOptions.centerCropTransform2 =\n          new GlideOptions().centerCrop().autoClone();\n    }\n    return GlideOptions.centerCropTransform2;\n  }\n\n  /**\n   * @see RequestOptions#circleCropTransform()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions circleCropTransform() {\n    if (GlideOptions.circleCropTransform3 == null) {\n      GlideOptions.circleCropTransform3 =\n          new GlideOptions().circleCrop().autoClone();\n    }\n    return GlideOptions.circleCropTransform3;\n  }\n\n  /**\n   * @see RequestOptions#bitmapTransform(Transformation)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new GlideOptions().transform(transformation);\n  }\n\n  /**\n   * @see RequestOptions#noTransformation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noTransformation() {\n    if (GlideOptions.noTransformation4 == null) {\n      GlideOptions.noTransformation4 =\n          new GlideOptions().dontTransform().autoClone();\n    }\n    return GlideOptions.noTransformation4;\n  }\n\n  /**\n   * @see RequestOptions#option(Option, T)\n   */\n  @CheckResult\n  @NonNull\n  public static <T> GlideOptions option(@NonNull Option<T> option, @NonNull T t) {\n    return new GlideOptions().set(option, t);\n  }\n\n  /**\n   * @see RequestOptions#decodeTypeOf(Class)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions decodeTypeOf(@NonNull Class<?> clazz) {\n    return new GlideOptions().decode(clazz);\n  }\n\n  /**\n   * @see RequestOptions#formatOf(DecodeFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions formatOf(@NonNull DecodeFormat format) {\n    return new GlideOptions().format(format);\n  }\n\n  /**\n   * @see RequestOptions#frameOf(long)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions frameOf(@IntRange(from = 0) long value) {\n    return new GlideOptions().frame(value);\n  }\n\n  /**\n   * @see RequestOptions#downsampleOf(DownsampleStrategy)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new GlideOptions().downsample(strategy);\n  }\n\n  /**\n   * @see RequestOptions#timeoutOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions timeoutOf(@IntRange(from = 0) int value) {\n    return new GlideOptions().timeout(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeQualityOf(int)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeQualityOf(@IntRange(from = 0, to = 100) int value) {\n    return new GlideOptions().encodeQuality(value);\n  }\n\n  /**\n   * @see RequestOptions#encodeFormatOf(CompressFormat)\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new GlideOptions().encodeFormat(format);\n  }\n\n  /**\n   * @see RequestOptions#noAnimation()\n   */\n  @CheckResult\n  @NonNull\n  public static GlideOptions noAnimation() {\n    if (GlideOptions.noAnimation5 == null) {\n      GlideOptions.noAnimation5 =\n          new GlideOptions().dontAnimate().autoClone();\n    }\n    return GlideOptions.noAnimation5;\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions sizeMultiplier(@FloatRange(from = 0.0, to = 1.0) float value) {\n    return (GlideOptions) super.sizeMultiplier(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useUnlimitedSourceGeneratorsPool(boolean flag) {\n    return (GlideOptions) super.useUnlimitedSourceGeneratorsPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions useAnimationPool(boolean flag) {\n    return (GlideOptions) super.useAnimationPool(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions onlyRetrieveFromCache(boolean flag) {\n    return (GlideOptions) super.onlyRetrieveFromCache(flag);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    return (GlideOptions) super.diskCacheStrategy(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions priority(@NonNull Priority priority) {\n    return (GlideOptions) super.priority(priority);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@Nullable Drawable drawable) {\n    return (GlideOptions) super.placeholder(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions placeholder(@DrawableRes int id) {\n    return (GlideOptions) super.placeholder(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@Nullable Drawable drawable) {\n    return (GlideOptions) super.fallback(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fallback(@DrawableRes int id) {\n    return (GlideOptions) super.fallback(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@Nullable Drawable drawable) {\n    return (GlideOptions) super.error(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions error(@DrawableRes int id) {\n    return (GlideOptions) super.error(id);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions theme(@Nullable Resources.Theme theme) {\n    return (GlideOptions) super.theme(theme);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions skipMemoryCache(boolean skip) {\n    return (GlideOptions) super.skipMemoryCache(skip);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int width, int height) {\n    return (GlideOptions) super.override(width, height);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions override(int size) {\n    return (GlideOptions) super.override(size);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions signature(@NonNull Key key) {\n    return (GlideOptions) super.signature(key);\n  }\n\n  @Override\n  @CheckResult\n  public GlideOptions clone() {\n    return (GlideOptions) super.clone();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions set(@NonNull Option<Y> option, @NonNull Y y) {\n    return (GlideOptions) super.set(option, y);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions decode(@NonNull Class<?> clazz) {\n    return (GlideOptions) super.decode(clazz);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return (GlideOptions) super.encodeFormat(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions encodeQuality(@IntRange(from = 0, to = 100) int value) {\n    return (GlideOptions) super.encodeQuality(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions frame(@IntRange(from = 0) long value) {\n    return (GlideOptions) super.frame(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions format(@NonNull DecodeFormat format) {\n    return (GlideOptions) super.format(format);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions disallowHardwareConfig() {\n    return (GlideOptions) super.disallowHardwareConfig();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions downsample(@NonNull DownsampleStrategy strategy) {\n    return (GlideOptions) super.downsample(strategy);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions timeout(@IntRange(from = 0) int value) {\n    return (GlideOptions) super.timeout(value);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterCrop() {\n    return (GlideOptions) super.optionalCenterCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerCrop() {\n    return (GlideOptions) super.centerCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalFitCenter() {\n    return (GlideOptions) super.optionalFitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions fitCenter() {\n    return (GlideOptions) super.fitCenter();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCenterInside() {\n    return (GlideOptions) super.optionalCenterInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions centerInside() {\n    return (GlideOptions) super.centerInside();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalCircleCrop() {\n    return (GlideOptions) super.optionalCircleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions circleCrop() {\n    return (GlideOptions) super.circleCrop();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions transform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.transform(transformation);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @NonNull\n  @CheckResult\n  public final GlideOptions transform(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transform(transformations);\n  }\n\n  @Override\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  @Deprecated\n  @NonNull\n  @CheckResult\n  public final GlideOptions transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return (GlideOptions) super.transforms(transformations);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return (GlideOptions) super.optionalTransform(transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions optionalTransform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.optionalTransform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public <Y> GlideOptions transform(@NonNull Class<Y> clazz,\n      @NonNull Transformation<Y> transformation) {\n    return (GlideOptions) super.transform(clazz, transformation);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontTransform() {\n    return (GlideOptions) super.dontTransform();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions dontAnimate() {\n    return (GlideOptions) super.dontAnimate();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideOptions apply(@NonNull BaseRequestOptions<?> options) {\n    return (GlideOptions) super.apply(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions lock() {\n    return (GlideOptions) super.lock();\n  }\n\n  @Override\n  @NonNull\n  public GlideOptions autoClone() {\n    return (GlideOptions) super.autoClone();\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/GlideExtensionWithTypeTest/GlideRequests.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.manager.Lifecycle;\nimport com.bumptech.glide.manager.RequestManagerTreeNode;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport java.io.File;\nimport java.net.URL;\n\n/**\n * Includes all additions from methods in {@link com.bumptech.glide.annotation.GlideExtension}s\n * annotated with {@link com.bumptech.glide.annotation.GlideType}\n *\n * <p>Generated code, do not modify\n */\n@SuppressWarnings(\"deprecation\")\npublic class GlideRequests extends RequestManager {\n  public GlideRequests(@NonNull Glide glide, @NonNull Lifecycle lifecycle,\n      @NonNull RequestManagerTreeNode treeNode, @NonNull Context context) {\n    super(glide, lifecycle, treeNode, context);\n  }\n\n  @Override\n  @CheckResult\n  @NonNull\n  public <ResourceType> GlideRequest<ResourceType> as(@NonNull Class<ResourceType> resourceClass) {\n    return new GlideRequest<>(glide, this, resourceClass, context);\n  }\n\n  /**\n   * @see ExtensionWithType#asNumber(RequestBuilder)\n   */\n  @NonNull\n  @CheckResult\n  public GlideRequest<Number> asNumber() {\n    return (GlideRequest<Number>) ExtensionWithType.asNumber(this.as(Number.class));\n  }\n\n  @Override\n  @NonNull\n  public synchronized GlideRequests applyDefaultRequestOptions(@NonNull RequestOptions options) {\n    return (GlideRequests) super.applyDefaultRequestOptions(options);\n  }\n\n  @Override\n  @NonNull\n  public synchronized GlideRequests setDefaultRequestOptions(@NonNull RequestOptions options) {\n    return (GlideRequests) super.setDefaultRequestOptions(options);\n  }\n\n  @Override\n  @NonNull\n  public GlideRequests addDefaultRequestListener(RequestListener<Object> listener) {\n    return (GlideRequests) super.addDefaultRequestListener(listener);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Bitmap> asBitmap() {\n    return (GlideRequest<Bitmap>) super.asBitmap();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<GifDrawable> asGif() {\n    return (GlideRequest<GifDrawable>) super.asGif();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> asDrawable() {\n    return (GlideRequest<Drawable>) super.asDrawable();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable Bitmap bitmap) {\n    return (GlideRequest<Drawable>) super.load(bitmap);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable Drawable drawable) {\n    return (GlideRequest<Drawable>) super.load(drawable);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable String string) {\n    return (GlideRequest<Drawable>) super.load(string);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable Uri uri) {\n    return (GlideRequest<Drawable>) super.load(uri);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable File file) {\n    return (GlideRequest<Drawable>) super.load(file);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@RawRes @DrawableRes @Nullable Integer id) {\n    return (GlideRequest<Drawable>) super.load(id);\n  }\n\n  @Override\n  @Deprecated\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable URL url) {\n    return (GlideRequest<Drawable>) super.load(url);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable byte[] bytes) {\n    return (GlideRequest<Drawable>) super.load(bytes);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<Drawable> load(@Nullable Object o) {\n    return (GlideRequest<Drawable>) super.load(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<File> downloadOnly() {\n    return (GlideRequest<File>) super.downloadOnly();\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<File> download(@Nullable Object o) {\n    return (GlideRequest<File>) super.download(o);\n  }\n\n  @Override\n  @NonNull\n  @CheckResult\n  public GlideRequest<File> asFile() {\n    return (GlideRequest<File>) super.asFile();\n  }\n\n  @Override\n  protected void setRequestOptions(@NonNull RequestOptions toSet) {\n    if (toSet instanceof com.bumptech.glide.test.GlideOptions) {\n      super.setRequestOptions(toSet);\n    } else {\n      super.setRequestOptions(new com.bumptech.glide.test.GlideOptions().apply(toSet));\n    }\n  }\n}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/MultipleAppGlideModuleTest/EmptyAppModule1.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\n\n@GlideModule\npublic final class EmptyAppModule1 extends AppGlideModule {}"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/MultipleAppGlideModuleTest/EmptyAppModule2.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\n\n@GlideModule\npublic final class EmptyAppModule2 extends AppGlideModule {}"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/MultipleEmptyLibraryGlideModuleTest/EmptyLibraryModule1.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\n\n@GlideModule\npublic final class EmptyLibraryModule1 extends LibraryGlideModule {}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/MultipleEmptyLibraryGlideModuleTest/EmptyLibraryModule2.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\n\n@GlideModule\npublic final class EmptyLibraryModule2 extends LibraryGlideModule {}\n"
  },
  {
    "path": "annotation/compiler/test/src/test/resources/MultipleEmptyLibraryGlideModuleTest/GlideIndexer_GlideModule_com_bumptech_glide_test_EmptyLibraryModule1_com_bumptech_glide_test_EmptyLibraryModule2.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\n@Index(\n    modules = {\n        \"com.bumptech.glide.test.EmptyLibraryModule1\",\n        \"com.bumptech.glide.test.EmptyLibraryModule2\"\n    }\n)\npublic class GlideIndexer_GlideModule_com_bumptech_glide_test_EmptyLibraryModule1_com_bumptech_glide_test_EmptyLibraryModule2 {\n}\n"
  },
  {
    "path": "annotation/gradle.properties",
    "content": "POM_NAME=Glide Annotations\nPOM_ARTIFACT_ID=annotations\nPOM_PACKAGING=jar\nPOM_DESCRIPTION=A set of annotations for configuring Glide.\n"
  },
  {
    "path": "annotation/ksp/build.gradle.kts",
    "content": "plugins {\n    id(\"org.jetbrains.kotlin.jvm\")\n    id(\"com.google.devtools.ksp\")\n}\n\nkotlin { jvmToolchain { languageVersion.set(JavaLanguageVersion.of(11)) } }\n\ndependencies {\n    implementation(libs.kotlinpoet)\n    implementation(project(\":annotation\"))\n    implementation(libs.ksp.api)\n    implementation(libs.autoservice.annotations)\n\n    ksp(libs.ksp.autoservice)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")\n"
  },
  {
    "path": "annotation/ksp/gradle.properties",
    "content": "kotlin.code.style=official\n\nPOM_NAME=Glide KSP Annotation Processor\nPOM_ARTIFACT_ID=ksp\nPOM_PACKAGING=jar\nPOM_DESCRIPTION=Glide's KSP based annotation processor. Should be included in all Kotlin applications and libraries that use Glide's modules for configuration and do not require the more advanced features of the Java based compiler.\n"
  },
  {
    "path": "annotation/ksp/integrationtest/build.gradle.kts",
    "content": "/**\n * This package verifies that our ksp processor is able to successfully import\n * and include LibraryGlideModules compiled in other modules. ksp:test is a more\n * comprehensive set of unit tests for other scenarios for library tests.\n *\n * <p>Technically we could include these integration tests in ksp:test. However\n * doing so would cause the dependent library to pollute every individual test\n * because it's pulled in from the classpath. Using a separate module allows us\n * to keep unit tests that are not concerned with dependent library modules\n * separate.\n */\n\nplugins {\n    id(\"org.jetbrains.kotlin.android\")\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.annotation.ksp.integrationtest\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n}\n\nkotlin {\n    jvmToolchain {\n        languageVersion.set(JavaLanguageVersion.of(11))\n    }\n}\n\ndependencies {\n    implementation(libs.junit)\n    testImplementation(project(\":annotation:ksp:test\"))\n    testImplementation(project(\":annotation:ksp\"))\n    testImplementation(project(\":annotation\"))\n    testImplementation(project(\":glide\"))\n    testImplementation(project(\":integration:okhttp3\"))\n    testImplementation(libs.ksp.compiletesting)\n    testImplementation(libs.truth)\n    testImplementation(libs.kotlin.test)\n    testImplementation(project(\":annotation:ksp:test\"))\n}"
  },
  {
    "path": "annotation/ksp/integrationtest/src/test/java/com/bumptech/glide/annotation/ksp/integrationtest/IntegrationLibraryGlideModuleTests.kt",
    "content": "package com.bumptech.glide.annotation.ksp.integrationtest\n\nimport com.bumptech.glide.annotation.ksp.test.CommonSources\nimport com.bumptech.glide.annotation.ksp.test.JavaSourceFile\nimport com.bumptech.glide.annotation.ksp.test.KotlinSourceFile\nimport com.bumptech.glide.annotation.ksp.test.PerSourceTypeTest\nimport com.bumptech.glide.annotation.ksp.test.SourceType\nimport com.bumptech.glide.annotation.ksp.test.hasSourceEqualTo\nimport com.google.common.truth.Truth.assertThat\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport org.junit.runners.Parameterized.Parameters\n\n@OptIn(ExperimentalCompilerApi::class)\n@RunWith(Parameterized::class)\nclass IntegrationLibraryGlideModuleTests(override val sourceType: SourceType) : PerSourceTypeTest {\n\n  companion object {\n    @Parameters(name = \"sourceType = {0}\") @JvmStatic fun data() = SourceType.values()\n  }\n\n  @Test\n  fun compile_withOnlyAppGlideModule_generatesGeneratedAppGlideModule_thatCallsDependencyLibraryGlideModules() {\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class AppModule extends AppGlideModule {\n            public AppModule() {}\n          }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(kotlinAppModule, javaAppModule) {\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithOnlyDependencyLibraryModules)\n    }\n  }\n\n  @Test\n  fun compile_withOnlyAppGlideModuleThroughBaseClass_generatesGeneratedAppGlideModule_thatCallsDependencyLibraryGlideModules() {\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        class BaseAppModule : AppGlideModule()\n        @GlideModule class AppModule : BaseAppModule()\n        \"\"\",\n      )\n    val javaBaseAppModule =\n      JavaSourceFile(\n        \"BaseAppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.module.AppGlideModule;\n\n        public class BaseAppModule extends AppGlideModule {\n          public BaseAppModule() {}\n        }\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n\n        @GlideModule public class AppModule extends BaseAppModule {\n          public AppModule() {}\n        }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(kotlinAppModule, javaBaseAppModule, javaAppModule) {\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithOnlyDependencyLibraryModules)\n    }\n  }\n\n  @Test\n  fun compile_withValidLibraryGlideModule_andAppGlideModule_generatesGeneratedAppGlideModule_thatCallsAllLibraryAndDependencyAndAppGlideModules() {\n    val kotlinLibraryModule =\n      KotlinSourceFile(\n        \"LibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val javaLibraryModule =\n      JavaSourceFile(\n        \"LibraryModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.LibraryGlideModule;\n          \n          @GlideModule public class LibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class AppModule extends AppGlideModule {\n            public AppModule() {}\n          }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinLibraryModule,\n      javaAppModule,\n      javaLibraryModule,\n    ) {\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithLibraryModuleAndDependencyLibraryModules)\n    }\n  }\n\n  @Test\n  fun compile_withValidLibraryGlideModule_andAppGlideModule_ThroughBaseClass_generatesGeneratedAppGlideModule_thatCallsAllLibraryAndDependencyAndAppGlideModules() {\n    val kotlinLibraryModule =\n      KotlinSourceFile(\n        \"LibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        class BaseLibraryModule : LibraryGlideModule()\n        @GlideModule class LibraryModule : BaseLibraryModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        class BaseAppModule : AppGlideModule()\n        @GlideModule class AppModule : BaseAppModule()\n        \"\"\",\n      )\n    val javaBaseLibraryModule =\n      JavaSourceFile(\n        \"BaseLibraryModule.java\",\n        \"\"\"\n          import com.bumptech.glide.module.LibraryGlideModule;\n\n          public class BaseLibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaLibraryModule =\n      JavaSourceFile(\n        \"LibraryModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n\n          @GlideModule public class LibraryModule extends BaseLibraryModule {}\n        \"\"\",\n      )\n    val javaBaseAppModule =\n      JavaSourceFile(\n        \"BaseAppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.module.AppGlideModule;\n\n          public class BaseAppModule extends AppGlideModule {\n            public BaseAppModule() {}\n          }\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n\n          @GlideModule public class AppModule extends BaseAppModule {\n            public AppModule() {}\n          }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinLibraryModule,\n      javaBaseAppModule,\n      javaAppModule,\n      javaBaseLibraryModule,\n      javaLibraryModule,\n    ) {\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithLibraryModuleAndDependencyLibraryModules)\n    }\n  }\n\n  @Test\n  fun compile_withDependencyModuleInExcludes_generatesGeneratedAppGlideModule_thatDoesNotCallDependencyLibraryGlideModules() {\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n        import com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule\n\n        @Excludes(OkHttpLibraryGlideModule::class) \n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.Excludes;\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          import com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule;\n          \n          @Excludes(OkHttpLibraryGlideModule.class)\n          @GlideModule \n          public class AppModule extends AppGlideModule {\n            public AppModule() {}\n          }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(kotlinAppModule, javaAppModule) {\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(CommonSources.simpleAppGlideModule)\n    }\n  }\n\n  @Test\n  fun compile_withLibraryModuleInExcludes_producesGeneratedAppGlideModuleThatDoesNotCallExcludedLibraryModule() {\n    val kotlinExcludedLibraryModule =\n      KotlinSourceFile(\n        \"ExcludedLibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class ExcludedLibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule \n        @Excludes(ExcludedLibraryModule::class) \n        class AppModule : AppGlideModule()\n        \"\"\",\n      )\n\n    val javaExcludedLibraryModule =\n      JavaSourceFile(\n        \"ExcludedLibraryModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        @GlideModule\n        public class ExcludedLibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes;\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.AppGlideModule;\n        \n        @GlideModule \n        @Excludes(ExcludedLibraryModule.class)\n        public class AppModule extends AppGlideModule {\n          public AppModule() {}\n        }\n        \"\"\",\n      )\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinExcludedLibraryModule,\n      javaAppModule,\n      javaExcludedLibraryModule,\n    ) {\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithOnlyDependencyLibraryModules)\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n    }\n  }\n}\n\n// generated code always includes public and Unit\n@Suppress(\"RedundantVisibilityModifier\", \"RedundantUnitReturnType\")\n@Language(\"kotlin\")\nconst val appGlideModuleWithLibraryModuleAndDependencyLibraryModules =\n  \"\"\"\npackage com.bumptech.glide\n\nimport AppModule\nimport LibraryModule\nimport android.content.Context\nimport com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule\nimport kotlin.Boolean\nimport kotlin.Suppress\nimport kotlin.Unit\n\ninternal class GeneratedAppGlideModuleImpl(\n  @Suppress(\"UNUSED_PARAMETER\")\n  context: Context,\n) : GeneratedAppGlideModule() {\n  private val appGlideModule: AppModule\n  init {\n    appGlideModule = AppModule()\n  }\n\n  public override fun registerComponents(\n    context: Context,\n    glide: Glide,\n    registry: Registry,\n  ): Unit {\n    OkHttpLibraryGlideModule().registerComponents(context, glide, registry)\n    LibraryModule().registerComponents(context, glide, registry)\n    appGlideModule.registerComponents(context, glide, registry)\n  }\n\n  public override fun applyOptions(context: Context, builder: GlideBuilder): Unit {\n    appGlideModule.applyOptions(context, builder)\n  }\n\n  public override fun isManifestParsingEnabled(): Boolean = false\n}\n\"\"\"\n\n// generated code always includes public and Unit\n@Suppress(\"RedundantVisibilityModifier\", \"RedundantUnitReturnType\")\n@Language(\"kotlin\")\nconst val appGlideModuleWithOnlyDependencyLibraryModules =\n  \"\"\"\npackage com.bumptech.glide\n\nimport AppModule\nimport android.content.Context\nimport com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule\nimport kotlin.Boolean\nimport kotlin.Suppress\nimport kotlin.Unit\n\ninternal class GeneratedAppGlideModuleImpl(\n  @Suppress(\"UNUSED_PARAMETER\")\n  context: Context,\n) : GeneratedAppGlideModule() {\n  private val appGlideModule: AppModule\n  init {\n    appGlideModule = AppModule()\n  }\n\n  public override fun registerComponents(\n    context: Context,\n    glide: Glide,\n    registry: Registry,\n  ): Unit {\n    OkHttpLibraryGlideModule().registerComponents(context, glide, registry)\n    appGlideModule.registerComponents(context, glide, registry)\n  }\n\n  public override fun applyOptions(context: Context, builder: GlideBuilder): Unit {\n    appGlideModule.applyOptions(context, builder)\n  }\n\n  public override fun isManifestParsingEnabled(): Boolean = false\n}\n\"\"\"\n"
  },
  {
    "path": "annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt",
    "content": "package com.bumptech.glide.annotation.ksp\n\nimport com.bumptech.glide.annotation.Excludes\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getConstructors\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimport com.google.devtools.ksp.symbol.KSAnnotation\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSDeclaration\nimport com.google.devtools.ksp.symbol.KSFunctionDeclaration\nimport com.google.devtools.ksp.symbol.KSNode\nimport com.google.devtools.ksp.symbol.KSType\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.TypeSpec\nimport kotlin.reflect.KClass\n\n// This class is visible only for testing\n// TODO(b/174783094): Add @VisibleForTesting when internal is supported.\nobject AppGlideModuleConstants {\n    // This variable is visible only for testing\n    // TODO(b/174783094): Add @VisibleForTesting when internal is supported.\n    const val INVALID_MODULE_MESSAGE =\n        \"Your AppGlideModule must have at least one constructor that has either no parameters or \" +\n            \"accepts only a Context.\"\n    // This variable is visible only for testing\n    // TODO(b/174783094): Add @VisibleForTesting when internal is supported.\n    const val INVALID_EXCLUDES_ANNOTATION_MESSAGE =\n        \"\"\"\n     @Excludes on %s is invalid. The value argument of your @Excludes annotation must be set to \n     either a single LibraryGlideModule class or a non-empty list of LibraryGlideModule classes. \n     Remove the annotation if you do not wish to exclude any LibraryGlideModules. Include each \n     LibraryGlideModule you do wish to exclude exactly once. Do not put types other than \n     LibraryGlideModules in the argument list\"\"\"\n\n    private const val CONTEXT_NAME = \"Context\"\n    private const val CONTEXT_PACKAGE = \"android.content\"\n    internal const val GLIDE_PACKAGE_NAME = \"com.bumptech.glide\"\n    internal const val CONTEXT_QUALIFIED_NAME = \"$CONTEXT_PACKAGE.$CONTEXT_NAME\"\n    internal const val GENERATED_ROOT_MODULE_PACKAGE_NAME = GLIDE_PACKAGE_NAME\n\n    internal val CONTEXT_CLASS_NAME = ClassName(CONTEXT_PACKAGE, CONTEXT_NAME)\n}\n\ninternal data class AppGlideModuleData(\n    val name: ClassName,\n    val constructor: Constructor,\n    val allowedLibraryGlideModuleNames: List<String>,\n    val sources: List<KSDeclaration>,\n) {\n    internal data class Constructor(val hasContext: Boolean)\n}\n\n/**\n * Given a [com.bumptech.glide.module.AppGlideModule] class declaration provided by the developer,\n * validate the class and produce a fully parsed [AppGlideModuleData] that allows us to generate a\n * valid [com.bumptech.glide.GeneratedAppGlideModule] implementation without further introspection.\n */\ninternal class AppGlideModuleParser(\n    private val environment: SymbolProcessorEnvironment,\n    private val resolver: Resolver,\n    private val appGlideModuleClass: KSClassDeclaration,\n) {\n\n    fun parseAppGlideModule(): AppGlideModuleData {\n        val constructor = parseAppGlideModuleConstructorOrThrow()\n        val name = ClassName.bestGuess(appGlideModuleClass.qualifiedName!!.asString())\n\n        val (indexFiles, allLibraryModuleNames) = getIndexesAndLibraryGlideModuleNames()\n        val excludedGlideModuleClassNames = getExcludedGlideModuleClassNames()\n        val filteredGlideModuleClassNames =\n            allLibraryModuleNames.filterNot { excludedGlideModuleClassNames.contains(it) }\n\n        return AppGlideModuleData(\n            name = name,\n            constructor = constructor,\n            allowedLibraryGlideModuleNames = filteredGlideModuleClassNames,\n            sources = indexFiles,\n        )\n    }\n\n    private fun getExcludedGlideModuleClassNames(): Set<String> {\n        val excludesAnnotation =\n            appGlideModuleClass.atMostOneExcludesAnnotation() ?: return emptySet()\n        environment.logger.logging(\n            \"Found excludes annotation arguments: ${excludesAnnotation.arguments}\"\n        )\n        return parseExcludesAnnotationArgumentsOrNull(excludesAnnotation)\n            ?: throw InvalidGlideSourceException(\n                AppGlideModuleConstants.INVALID_EXCLUDES_ANNOTATION_MESSAGE.format(\n                    appGlideModuleClass.qualifiedName?.asString()\n                )\n            )\n    }\n\n    /**\n     * Given a list of arguments from an [com.bumptech.glide.annotation.Excludes] annotation, parses\n     * and returns a list of qualified names of the excluded\n     * [com.bumptech.glide.module.LibraryGlideModule] implementations, or returns null if the\n     * arguments are invalid.\n     *\n     * Ideally we'd throw more specific exceptions based on the type of failure. However, there are\n     * a bunch of individual failure types and they differ depending on whether the source was\n     * written in Java or Kotlin. Rather than trying to describe every failure in detail, we'll just\n     * return null and allow callers to describe the correct behavior.\n     */\n    private fun parseExcludesAnnotationArgumentsOrNull(\n        excludesAnnotation: KSAnnotation\n    ): Set<String>? {\n        val valueArguments: List<KSType>? = excludesAnnotation.valueArgumentList()\n        if (valueArguments == null || valueArguments.isEmpty()) {\n            return null\n        }\n        if (valueArguments.any { !it.extendsLibraryGlideModule() }) {\n            return null\n        }\n        val libraryGlideModuleNames =\n            valueArguments.mapNotNull { it.declaration.qualifiedName?.asString() }\n        if (libraryGlideModuleNames.size != valueArguments.size) {\n            return null\n        }\n        val uniqueLibraryGlideModuleNames = libraryGlideModuleNames.toSet()\n        if (uniqueLibraryGlideModuleNames.size != valueArguments.size) {\n            return null\n        }\n        return uniqueLibraryGlideModuleNames\n    }\n\n    private fun KSType.extendsLibraryGlideModule(): Boolean =\n        ModuleParser.extractGlideModules(listOf<KSNode>(declaration)).libraryModules.size == 1\n\n    /**\n     * Parses the `value` argument as a list of the given type, or returns `null` if the annotation\n     * has multiple arguments or `value` has any entries that are not of the expected type `T`.\n     *\n     * `value` is the name of the default annotation parameter allowed by syntax like\n     * `@Excludes(argument)` or `@Excludes(argument1, argument2)` or `@Excludes({argument1,\n     * argument2})`, depending on the source type (Kotlin or Java). This method requires that the\n     * annotation has exactly one `value` argument of a given type and standardizes the differences\n     * KSP produces between Kotlin and Java source.\n     *\n     * To make this function more general purpose, we should assert that the values are of type T\n     * rather just returning null. For our current single use case, returning null matches the use\n     * case for the caller better than throwing.\n     */\n    private inline fun <reified T> KSAnnotation.valueArgumentList(): List<T>? {\n        // Require that the annotation has a single value argument that points either to a single\n        // thing\n        // or a list of things (A or [A, B, C]). First validate that there's exactly one parameter\n        // and\n        // that it has the expected name.\n        // e.g. @Excludes(value = (A or [A, B, C])) -> (A or [A, B, C])\n        val valueParameterValue: Any? =\n            arguments.singleOrNull().takeIf { it?.name?.asString() == \"value\" }?.value\n\n        // Next unify the types by verifying that it either has a single value of T, or a List of\n        // T and converting both to List<T>\n        // (A or [A, B, C]) -> ([A] or [A, B, C]) with the correct types\n        return when (valueParameterValue) {\n            is List<*> -> valueParameterValue.asListGivenTypeOfOrNull()\n            is T -> listOf(valueParameterValue)\n            else -> null\n        }\n    }\n\n    private inline fun <reified T> List<*>.asListGivenTypeOfOrNull(): List<T>? =\n        filterIsInstance(T::class.java).takeIf { it.size == size }\n\n    private fun parseAppGlideModuleConstructorOrThrow(): AppGlideModuleData.Constructor {\n        val hasEmptyConstructors =\n            appGlideModuleClass.getConstructors().any { it.parameters.isEmpty() }\n        val hasContextParamOnlyConstructor =\n            appGlideModuleClass.getConstructors().any { it.hasSingleContextParameter() }\n        if (!hasEmptyConstructors && !hasContextParamOnlyConstructor) {\n            throw InvalidGlideSourceException(AppGlideModuleConstants.INVALID_MODULE_MESSAGE)\n        }\n        return AppGlideModuleData.Constructor(hasContextParamOnlyConstructor)\n    }\n\n    private fun KSFunctionDeclaration.hasSingleContextParameter() =\n        parameters.size == 1 &&\n            AppGlideModuleConstants.CONTEXT_QUALIFIED_NAME ==\n                parameters.single().type.resolve().declaration.qualifiedName?.asString()\n\n    private data class IndexFilesAndLibraryModuleNames(\n        val indexFiles: List<KSDeclaration>,\n        val libraryModuleNames: List<String>,\n    )\n\n    @OptIn(KspExperimental::class)\n    private fun getIndexesAndLibraryGlideModuleNames(): IndexFilesAndLibraryModuleNames {\n        val allIndexFiles: MutableList<KSDeclaration> = mutableListOf()\n        val allLibraryGlideModuleNames: MutableList<String> = mutableListOf()\n\n        val allIndexesAndLibraryModules =\n            getAllLibraryNamesFromJavaIndexes() + getAllLibraryNamesFromKspIndexes()\n        for ((index, libraryGlideModuleNames) in allIndexesAndLibraryModules) {\n            allIndexFiles.add(index)\n            allLibraryGlideModuleNames.addAll(libraryGlideModuleNames)\n        }\n\n        return IndexFilesAndLibraryModuleNames(allIndexFiles, allLibraryGlideModuleNames)\n    }\n\n    internal data class IndexAndLibraryModuleNames(\n        val index: KSDeclaration,\n        val libraryModuleNames: List<String>,\n    )\n\n    private fun getAllLibraryNamesFromKspIndexes(): List<IndexAndLibraryModuleNames> =\n        getAllLibraryNamesFromIndexes(GlideSymbolProcessorConstants.PACKAGE_NAME) { index ->\n            extractGlideModulesFromKspIndexAnnotation(index)\n        }\n\n    private fun getAllLibraryNamesFromJavaIndexes(): List<IndexAndLibraryModuleNames> =\n        getAllLibraryNamesFromIndexes(GlideSymbolProcessorConstants.JAVA_ANNOTATION_PACKAGE_NAME) {\n            index ->\n            extractGlideModulesFromJavaIndexAnnotation(index)\n        }\n\n    @OptIn(KspExperimental::class)\n    private fun getAllLibraryNamesFromIndexes(\n        packageName: String,\n        extractLibraryModuleNamesFromIndex: (KSDeclaration) -> List<String>,\n    ) = buildList {\n        resolver.getDeclarationsFromPackage(packageName).forEach { index: KSDeclaration ->\n            val libraryGlideModuleNames = extractLibraryModuleNamesFromIndex(index)\n            if (libraryGlideModuleNames.isNotEmpty()) {\n                environment.logger.info(\n                    \"Found index annotation: $index with modules: $libraryGlideModuleNames\"\n                )\n                add(IndexAndLibraryModuleNames(index, libraryGlideModuleNames))\n            }\n        }\n    }\n\n    private fun extractGlideModulesFromJavaIndexAnnotation(index: KSDeclaration): List<String> {\n        val indexAnnotation: KSAnnotation =\n            index.atMostOneJavaIndexAnnotation() ?: return emptyList()\n        return indexAnnotation.getModuleArgumentValues().toList()\n    }\n\n    private fun extractGlideModulesFromKspIndexAnnotation(index: KSDeclaration): List<String> {\n        val indexAnnotation: KSAnnotation =\n            index.atMostOneKspIndexAnnotation() ?: return emptyList()\n        return indexAnnotation.getModuleArgumentValues().toList()\n    }\n\n    private fun KSAnnotation.getModuleArgumentValues(): List<String> {\n        val result =\n            arguments\n                .find { it.name?.getShortName().equals(IndexGenerator.INDEX_MODULES_NAME) }\n                ?.value\n        if (result is List<*> && result.all { it is String }) {\n            @Suppress(\"UNCHECKED_CAST\")\n            return result as List<String>\n        }\n        throw InvalidGlideSourceException(\"Found an invalid internal Glide index: $this\")\n    }\n\n    private fun KSDeclaration.atMostOneJavaIndexAnnotation() =\n        atMostOneAnnotation(\"com.bumptech.glide.annotation.compiler.Index\")\n\n    private fun KSDeclaration.atMostOneKspIndexAnnotation() = atMostOneAnnotation(Index::class)\n\n    private fun KSDeclaration.atMostOneExcludesAnnotation() = atMostOneAnnotation(Excludes::class)\n\n    private fun KSDeclaration.atMostOneAnnotation(\n        annotation: KClass<out Annotation>\n    ): KSAnnotation? = atMostOneAnnotation(annotation.qualifiedName)\n\n    private fun KSDeclaration.atMostOneAnnotation(annotationQualifiedName: String?): KSAnnotation? {\n        val matchingAnnotations: List<KSAnnotation> =\n            annotations\n                .filter {\n                    annotationQualifiedName?.equals(\n                        it.annotationType.resolve().declaration.qualifiedName?.asString()\n                    ) ?: false\n                }\n                .toList()\n        if (matchingAnnotations.size > 1) {\n            throw InvalidGlideSourceException(\n                \"\"\"Expected 0 or 1 $annotationQualifiedName annotations on $qualifiedName, but found: \n          ${matchingAnnotations.size}\"\"\"\n            )\n        }\n        return matchingAnnotations.singleOrNull()\n    }\n}\n\n/**\n * Given valid [AppGlideModuleData], writes a Kotlin implementation of\n * [com.bumptech.glide.GeneratedAppGlideModule].\n *\n * This class should obtain all of its data from [AppGlideModuleData] and should not interact with\n * any ksp classes. In the long run, the restriction may allow us to share code between the Java and\n * Kotlin processors.\n */\ninternal class AppGlideModuleGenerator(private val appGlideModuleData: AppGlideModuleData) {\n\n    fun generateAppGlideModule(): FileSpec {\n        val generatedAppGlideModuleClass = generateAppGlideModuleClass(appGlideModuleData)\n        return FileSpec.builder(\n                AppGlideModuleConstants.GLIDE_PACKAGE_NAME,\n                \"GeneratedAppGlideModuleImpl\",\n            )\n            .addType(generatedAppGlideModuleClass)\n            .build()\n    }\n\n    private fun generateAppGlideModuleClass(data: AppGlideModuleData): TypeSpec {\n        return TypeSpec.classBuilder(\"GeneratedAppGlideModuleImpl\")\n            .superclass(\n                ClassName(\n                    AppGlideModuleConstants.GENERATED_ROOT_MODULE_PACKAGE_NAME,\n                    \"GeneratedAppGlideModule\",\n                )\n            )\n            .addModifiers(KModifier.INTERNAL)\n            .addProperty(\"appGlideModule\", data.name, KModifier.PRIVATE)\n            .primaryConstructor(generateConstructor(data))\n            .addFunction(generateRegisterComponents(data.allowedLibraryGlideModuleNames))\n            .addFunction(generateApplyOptions())\n            .addFunction(generateManifestParsingDisabled())\n            .build()\n    }\n\n    private fun generateConstructor(data: AppGlideModuleData): FunSpec {\n        val contextParameterBuilder =\n            ParameterSpec.builder(\"context\", AppGlideModuleConstants.CONTEXT_CLASS_NAME)\n        if (!data.constructor.hasContext) {\n            contextParameterBuilder.addAnnotation(\n                AnnotationSpec.builder(ClassName(\"kotlin\", \"Suppress\"))\n                    .addMember(\"%S\", \"UNUSED_PARAMETER\")\n                    .build()\n            )\n        }\n\n        return FunSpec.constructorBuilder()\n            .addParameter(contextParameterBuilder.build())\n            .addStatement(\n                \"appGlideModule = %T(${if (data.constructor.hasContext) \"context\" else \"\"})\",\n                data.name,\n            )\n            .build()\n\n        // TODO(judds): Log the discovered modules here.\n    }\n\n    private fun generateRegisterComponents(allowedGlideModuleNames: List<String>) =\n        FunSpec.builder(\"registerComponents\")\n            .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)\n            .addParameter(\"context\", AppGlideModuleConstants.CONTEXT_CLASS_NAME)\n            .addParameter(\"glide\", ClassName(AppGlideModuleConstants.GLIDE_PACKAGE_NAME, \"Glide\"))\n            .addParameter(\n                \"registry\",\n                ClassName(AppGlideModuleConstants.GLIDE_PACKAGE_NAME, \"Registry\"),\n            )\n            .apply {\n                allowedGlideModuleNames.forEach {\n                    addStatement(\n                        \"%T().registerComponents(context, glide, registry)\",\n                        ClassName.bestGuess(it),\n                    )\n                }\n            }\n            .addStatement(\"appGlideModule.registerComponents(context, glide, registry)\")\n            .build()\n\n    private fun generateApplyOptions() =\n        FunSpec.builder(\"applyOptions\")\n            .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)\n            .addParameter(\"context\", AppGlideModuleConstants.CONTEXT_CLASS_NAME)\n            .addParameter(\n                \"builder\",\n                ClassName(AppGlideModuleConstants.GLIDE_PACKAGE_NAME, \"GlideBuilder\"),\n            )\n            .addStatement(\"appGlideModule.applyOptions(context, builder)\")\n            .build()\n\n    private fun generateManifestParsingDisabled() =\n        FunSpec.builder(\"isManifestParsingEnabled\")\n            .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)\n            .returns(Boolean::class)\n            .addStatement(\"return false\")\n            .build()\n}\n"
  },
  {
    "path": "annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt",
    "content": "package com.bumptech.glide.annotation.ksp\n\nimport com.google.devtools.ksp.processing.Dependencies\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.validate\nimport com.squareup.kotlinpoet.FileSpec\n\n/**\n * Glide's KSP annotation processor.\n *\n * This class recognizes and parses [com.bumptech.glide.module.AppGlideModule]s and\n * [com.bumptech.glide.module.LibraryGlideModule]s that are annotated with\n * [com.bumptech.glide.annotation.GlideModule].\n *\n * `LibraryGlideModule`s are merged into indexes, or classes generated in Glide's package. When a\n * `AppGlideModule` is found, we then generate Glide's configuration so that it calls the\n * `AppGlideModule` and any included `LibraryGlideModules`. Using indexes allows us to process\n * `LibraryGlideModules` in multiple rounds and/or libraries.\n */\nclass GlideSymbolProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor {\n    private var isAppGlideModuleGenerated = false\n\n    override fun process(resolver: Resolver): List<KSAnnotated> {\n        val symbols = resolver.getSymbolsWithAnnotation(\"com.bumptech.glide.annotation.GlideModule\")\n        val (validSymbols, invalidSymbols) = symbols.partition { it.validate() }.toList()\n        return try {\n            processChecked(resolver, symbols, validSymbols, invalidSymbols)\n        } catch (e: InvalidGlideSourceException) {\n            environment.logger.error(e.userMessage)\n            invalidSymbols\n        }\n    }\n\n    private fun processChecked(\n        resolver: Resolver,\n        symbols: Sequence<KSAnnotated>,\n        validSymbols: List<KSAnnotated>,\n        invalidSymbols: List<KSAnnotated>,\n    ): List<KSAnnotated> {\n        environment.logger.logging(\"Found symbols, valid: $validSymbols, invalid: $invalidSymbols\")\n\n        val (appGlideModules, libraryGlideModules) = ModuleParser.extractGlideModules(validSymbols)\n\n        if (libraryGlideModules.size + appGlideModules.size != validSymbols.count()) {\n            val invalidModules =\n                symbols\n                    .filter { !libraryGlideModules.contains(it) && !appGlideModules.contains(it) }\n                    .map { it.location.toString() }\n                    .toList()\n\n            throw InvalidGlideSourceException(\n                GlideSymbolProcessorConstants.INVALID_ANNOTATED_CLASS.format(invalidModules)\n            )\n        }\n\n        if (appGlideModules.size > 1) {\n            throw InvalidGlideSourceException(\n                GlideSymbolProcessorConstants.SINGLE_APP_MODULE_ERROR.format(appGlideModules)\n            )\n        }\n\n        environment.logger.logging(\n            \"Found AppGlideModules: $appGlideModules, LibraryGlideModules: $libraryGlideModules\"\n        )\n\n        if (libraryGlideModules.isNotEmpty()) {\n            if (isAppGlideModuleGenerated) {\n                throw InvalidGlideSourceException(\n                    \"\"\"Found $libraryGlideModules LibraryGlideModules after processing the AppGlideModule.\n            If you generated these LibraryGlideModules via another annotation processing, either\n            don't or also generate the AppGlideModule and do so in the same round as the \n            LibraryGlideModules or in a subsequent round\"\"\"\n                )\n            }\n            parseLibraryModulesAndWriteIndex(libraryGlideModules)\n            return invalidSymbols + appGlideModules\n        }\n\n        if (appGlideModules.isNotEmpty()) {\n            parseAppGlideModuleAndIndexesAndWriteGeneratedAppGlideModule(\n                resolver,\n                appGlideModules.single(),\n            )\n        }\n\n        return invalidSymbols\n    }\n\n    private fun parseAppGlideModuleAndIndexesAndWriteGeneratedAppGlideModule(\n        resolver: Resolver,\n        appGlideModule: KSClassDeclaration,\n    ) {\n        val appGlideModuleData =\n            AppGlideModuleParser(environment, resolver, appGlideModule).parseAppGlideModule()\n        val appGlideModuleGenerator = AppGlideModuleGenerator(appGlideModuleData)\n        val appGlideModuleFileSpec: FileSpec = appGlideModuleGenerator.generateAppGlideModule()\n        val sources = appGlideModuleData.sources.mapNotNull { it.containingFile }.toMutableList()\n        if (appGlideModule.containingFile != null) {\n            sources.add(appGlideModule.containingFile!!)\n        }\n        writeFile(appGlideModuleFileSpec, sources)\n    }\n\n    private fun parseLibraryModulesAndWriteIndex(\n        libraryGlideModuleClassDeclarations: List<KSClassDeclaration>\n    ) {\n        val libraryGlideModulesParser =\n            LibraryGlideModulesParser(environment, libraryGlideModuleClassDeclarations)\n        val uniqueLibraryGlideModules = libraryGlideModulesParser.parseUnique()\n        val index: FileSpec = IndexGenerator.generate(uniqueLibraryGlideModules.map { it.name })\n        writeFile(index, uniqueLibraryGlideModules.mapNotNull { it.containingFile })\n    }\n\n    private fun writeFile(file: FileSpec, sources: List<KSFile>) {\n        environment.codeGenerator\n            .createNewFile(\n                Dependencies(aggregating = false, sources = sources.toTypedArray()),\n                file.packageName,\n                file.name,\n            )\n            .writer()\n            .use { file.writeTo(it) }\n\n        environment.logger.logging(\"Wrote file: $file\")\n    }\n}\n\n// This class is visible only for testing\n// TODO(b/174783094): Add @VisibleForTesting when internal is supported.\nobject GlideSymbolProcessorConstants {\n    // This variable is visible only for testing\n    // TODO(b/174783094): Add @VisibleForTesting when internal is supported.\n    val PACKAGE_NAME: String = GlideSymbolProcessor::class.java.`package`.name\n    val JAVA_ANNOTATION_PACKAGE_NAME: String = \"com.bumptech.glide.annotation.compiler\"\n    const val SINGLE_APP_MODULE_ERROR = \"You can have at most one AppGlideModule, but found: %s\"\n    const val DUPLICATE_LIBRARY_MODULE_ERROR =\n        \"LibraryGlideModules %s are included more than once, keeping only one!\"\n    const val INVALID_ANNOTATED_CLASS =\n        \"@GlideModule annotated classes must implement AppGlideModule or LibraryGlideModule: %s\"\n}\n\ninternal class InvalidGlideSourceException(val userMessage: String) : Exception(userMessage)\n"
  },
  {
    "path": "annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessorProvider.kt",
    "content": "package com.bumptech.glide.annotation.ksp\n\nimport com.google.auto.service.AutoService\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimport com.google.devtools.ksp.processing.SymbolProcessorProvider\n\n@AutoService(SymbolProcessorProvider::class)\nclass GlideSymbolProcessorProvider : SymbolProcessorProvider {\n    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {\n        return GlideSymbolProcessor(environment)\n    }\n}\n"
  },
  {
    "path": "annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModules.kt",
    "content": "package com.bumptech.glide.annotation.ksp\n\nimport com.bumptech.glide.annotation.GlideModule\nimport com.bumptech.glide.annotation.ksp.LibraryGlideModuleData.LibraryModuleName\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.DelicateKotlinPoetApi\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.TypeSpec\nimport java.util.UUID\n\ninternal data class LibraryGlideModuleData(\n    val name: LibraryModuleName,\n    val containingFile: KSFile?,\n) {\n    data class LibraryModuleName(val qualifiedName: String)\n}\n\ninternal class LibraryGlideModulesParser(\n    private val environment: SymbolProcessorEnvironment,\n    private val libraryGlideModules: List<KSClassDeclaration>,\n) {\n    init {\n        require(libraryGlideModules.isNotEmpty())\n    }\n\n    fun parseUnique(): List<LibraryGlideModuleData> {\n        val allLibraryGlideModules =\n            libraryGlideModules\n                .map {\n                    LibraryGlideModuleData(\n                        LibraryModuleName(it.qualifiedName!!.asString()),\n                        it.containingFile,\n                    )\n                }\n                .toList()\n        val uniqueLibraryGlideModules =\n            allLibraryGlideModules.associateBy { it.name }.values.toList()\n        if (uniqueLibraryGlideModules.size != libraryGlideModules.size) {\n            // Find the set of modules that have been included more than once by mapping the\n            // qualified\n            // name of the module to a count of the number of times it's been seen. Duplicates are\n            // then\n            // any keys that have a value > 1.\n            val duplicateModules: List<String> =\n                allLibraryGlideModules\n                    .groupingBy { it.name.qualifiedName }\n                    .eachCount()\n                    .filter { it.value > 1 }\n                    .keys\n                    .toList()\n            environment.logger.warn(\n                GlideSymbolProcessorConstants.DUPLICATE_LIBRARY_MODULE_ERROR.format(\n                    duplicateModules\n                )\n            )\n        }\n\n        return uniqueLibraryGlideModules\n    }\n}\n\n/**\n * Generates an empty class with an annotation containing the class names of one or more\n * LibraryGlideModules and/or one or more GlideExtensions.\n *\n * We use a separate class so that LibraryGlideModules and GlideExtensions written in libraries can\n * be bundled into an AAR and later retrieved by the annotation processor when it processes the\n * AppGlideModule in an application.\n *\n * The output file generated by this class with a single LibraryGlideModule looks like this:\n * ```\n * @com.bumptech.glide.annotation.ksp.Index(\n *   [\"com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule\"]\n * )\n * class Indexer_GlideModule_com_bumptech_glide_integration_okhttp3_OkHttpLibraryGlideModule\n * ```\n *\n * This class is not a public API and used only internally by the processor.\n */\ninternal object IndexGenerator {\n    private const val INDEXER_NAME_PREFIX = \"GlideIndexer_\"\n    private const val MAXIMUM_FILE_NAME_LENGTH = 255\n\n    // The name of the parameter in the Index annotation that points to the list of modules\n    internal const val INDEX_MODULES_NAME = \"modules\"\n\n    @OptIn(DelicateKotlinPoetApi::class) // For AnnotationSpec.builder\n    fun generate(libraryModuleNames: List<LibraryModuleName>): FileSpec {\n        val libraryModuleQualifiedNames: List<String> = libraryModuleNames.map { it.qualifiedName }\n\n        val indexAnnotation: AnnotationSpec =\n            AnnotationSpec.builder(Index::class.java)\n                .addRepeatedMember(INDEX_MODULES_NAME, libraryModuleQualifiedNames)\n                .build()\n        val indexName = generateUniqueName(libraryModuleQualifiedNames)\n\n        return FileSpec.builder(GlideSymbolProcessorConstants.PACKAGE_NAME, indexName)\n            .addType(TypeSpec.classBuilder(indexName).addAnnotation(indexAnnotation).build())\n            .build()\n    }\n\n    private fun generateUniqueName(libraryModuleQualifiedNames: List<String>): String {\n        val glideModuleBasedName = generateNameFromLibraryModules(libraryModuleQualifiedNames)\n\n        // If the indexer name has too many packages/modules, it can exceed the file name length\n        // allowed by the file system, which can break compilation. To avoid that, fall back to a\n        // deterministic UUID.\n        return if (glideModuleBasedName.exceedsFileSystemMaxNameLength()) {\n            generateShortUUIDBasedName(glideModuleBasedName)\n        } else {\n            glideModuleBasedName\n        }\n    }\n\n    private fun String.exceedsFileSystemMaxNameLength() =\n        length >= MAXIMUM_FILE_NAME_LENGTH - INDEXER_NAME_PREFIX.length\n\n    private fun generateShortUUIDBasedName(glideModuleBasedName: String) =\n        INDEXER_NAME_PREFIX +\n            UUID.nameUUIDFromBytes(glideModuleBasedName.toByteArray()).toString().replace(\"-\", \"_\")\n\n    private fun generateNameFromLibraryModules(libraryModuleQualifiedNames: List<String>): String {\n        return libraryModuleQualifiedNames.joinToString(\n            prefix = INDEXER_NAME_PREFIX + GlideModule::class.java.simpleName + \"_\",\n            separator = \"_\",\n        ) {\n            it.replace(\".\", \"_\")\n        }\n    }\n\n    private fun AnnotationSpec.Builder.addRepeatedMember(\n        name: String,\n        repeatedMember: List<String>,\n    ) =\n        addMember(\n            \"$name = [\\n\" + \"%S,\\n\".repeat(repeatedMember.size) + \"]\",\n            *repeatedMember.toTypedArray(),\n        )\n}\n"
  },
  {
    "path": "annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/ModuleParser.kt",
    "content": "package com.bumptech.glide.annotation.ksp\n\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSNode\n\nobject ModuleParser {\n\n    internal data class GlideModules(\n        val appModules: List<KSClassDeclaration>,\n        val libraryModules: List<KSClassDeclaration>,\n    )\n\n    internal fun extractGlideModules(annotatedModules: List<KSNode>): GlideModules {\n        val appAndLibraryModuleNames =\n            listOf(APP_MODULE_QUALIFIED_NAME, LIBRARY_MODULE_QUALIFIED_NAME)\n        val modulesBySuperType: Map<String?, List<KSClassDeclaration>> =\n            annotatedModules.filterIsInstance<KSClassDeclaration>().groupBy { classDeclaration ->\n                appAndLibraryModuleNames.firstOrNull { classDeclaration.hasSuperType(it) }\n            }\n\n        val (appModules, libraryModules) =\n            appAndLibraryModuleNames.map { modulesBySuperType[it] ?: emptyList() }\n        return GlideModules(appModules, libraryModules)\n    }\n\n    private fun KSClassDeclaration.hasSuperType(superTypeQualifiedName: String): Boolean {\n        val superDeclarations = superTypes.map { superType -> superType.resolve().declaration }\n        val hasInDirectParent =\n            superDeclarations.map { it.qualifiedName!!.asString() }.contains(superTypeQualifiedName)\n        return if (hasInDirectParent) {\n            true\n        } else {\n            superDeclarations.filterIsInstance(KSClassDeclaration::class.java).any {\n                it.hasSuperType(superTypeQualifiedName)\n            }\n        }\n    }\n\n    private const val APP_MODULE_QUALIFIED_NAME = \"com.bumptech.glide.module.AppGlideModule\"\n    private const val LIBRARY_MODULE_QUALIFIED_NAME = \"com.bumptech.glide.module.LibraryGlideModule\"\n}\n"
  },
  {
    "path": "annotation/ksp/test/build.gradle.kts",
    "content": "plugins {\n    id(\"org.jetbrains.kotlin.android\")\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.annotation.ksp.test\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n}\n\nkotlin {\n    jvmToolchain {\n        languageVersion.set(JavaLanguageVersion.of(11))\n    }\n}\n\ndependencies {\n    implementation(libs.junit)\n    implementation(project(\":annotation:ksp\"))\n    implementation(libs.ksp.compiletesting)\n    implementation(libs.truth)\n\n    testImplementation(project(\":annotation:ksp\"))\n    testImplementation(project(\":annotation\"))\n    testImplementation(project(\":glide\"))\n    testImplementation(libs.kotlin.test)\n}"
  },
  {
    "path": "annotation/ksp/test/src/main/kotlin/com/bumptech/glide/annotation/ksp/test/SourceTestHelpers.kt",
    "content": "package com.bumptech.glide.annotation.ksp.test\n\nimport com.bumptech.glide.annotation.ksp.GlideSymbolProcessorProvider\nimport com.google.common.truth.StringSubject\nimport com.tschuchort.compiletesting.KotlinCompilation\nimport com.tschuchort.compiletesting.SourceFile\nimport com.tschuchort.compiletesting.kspSourcesDir\nimport com.tschuchort.compiletesting.symbolProcessorProviders\nimport java.io.File\nimport java.io.FileNotFoundException\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\n\n@OptIn(ExperimentalCompilerApi::class)\nclass CompilationResult(\n  private val compilation: KotlinCompilation,\n  result: KotlinCompilation.Result,\n) {\n  val exitCode = result.exitCode\n  val messages = result.messages\n\n  fun generatedAppGlideModuleContents() = readFile(findAppGlideModule())\n\n  fun allGeneratedFiles(): List<File> {\n    val allFiles = mutableListOf<File>()\n    val parentDir = generatedFilesParentDir()\n    if (parentDir != null) {\n      findAllFilesRecursive(parentDir, allFiles)\n    }\n    return allFiles\n  }\n\n  private fun findAllFilesRecursive(parent: File, allFiles: MutableList<File>) {\n    if (parent.isFile) {\n      allFiles.add(parent)\n      return\n    }\n    parent.listFiles()?.map { findAllFilesRecursive(it, allFiles) }\n  }\n\n  private fun generatedFilesParentDir(): File? {\n    var currentDir: File? = compilation.kspSourcesDir\n    listOf(\"kotlin\", \"com\", \"bumptech\", \"glide\").forEach { directoryName ->\n      currentDir = currentDir?.listFiles()?.find { it.name.equals(directoryName) }\n    }\n    return currentDir\n  }\n\n  private fun readFile(file: File) = file.readLines().joinToString(\"\\n\")\n\n  private fun findAppGlideModule(): File {\n    return generatedFilesParentDir()?.listFiles()?.find {\n      it.name.equals(\"GeneratedAppGlideModuleImpl.kt\")\n    }\n      ?: throw FileNotFoundException(\n        \"GeneratedAppGlideModuleImpl.kt was not generated or not generated in the expected\" +\n          \"location\"\n      )\n  }\n}\n\nenum class SourceType {\n  KOTLIN,\n  JAVA\n}\n\nsealed interface TypedSourceFile {\n  fun sourceFile(): SourceFile\n  fun sourceType(): SourceType\n}\n\nclass GeneratedSourceFile(\n  private val file: File,\n  private val currentSourceType: SourceType,\n) : TypedSourceFile {\n  override fun sourceFile(): SourceFile = SourceFile.fromPath(file)\n\n  // Hack alert: We use this class only for generated output of some previous compilation. We rely\n  // on the type in that previous compilation to select the proper source. The output however is\n  // always Kotlin, regardless of source. But we always want to include whatever the generated\n  // output is in the next step. That means we need our sourceType here to match the\n  //  currentSourceType in the test.\n  override fun sourceType(): SourceType = currentSourceType\n}\n\nclass KotlinSourceFile(\n  val name: String,\n  @Language(\"kotlin\") val content: String,\n) : TypedSourceFile {\n  override fun sourceFile() = SourceFile.kotlin(name, content)\n  override fun sourceType() = SourceType.KOTLIN\n}\n\nclass JavaSourceFile(\n  val name: String,\n  @Language(\"java\") val content: String,\n) : TypedSourceFile {\n  override fun sourceFile() = SourceFile.java(name, content)\n  override fun sourceType() = SourceType.JAVA\n}\n\ninterface PerSourceTypeTest {\n  val sourceType: SourceType\n\n  fun compileCurrentSourceType(\n    vararg sourceFiles: TypedSourceFile,\n    test: (input: CompilationResult) -> Unit = {},\n  ): CompilationResult {\n    val result =\n      compile(sourceFiles.filter { it.sourceType() == sourceType }.map { it.sourceFile() }.toList())\n    test(result)\n    return result\n  }\n}\n\n@OptIn(ExperimentalCompilerApi::class)\ninternal fun compile(sourceFiles: List<SourceFile>): CompilationResult {\n  require(sourceFiles.isNotEmpty())\n  val compilation =\n    KotlinCompilation().apply {\n      sources = sourceFiles\n      symbolProcessorProviders = listOf(GlideSymbolProcessorProvider())\n      inheritClassPath = true\n    }\n  val result = compilation.compile()\n  return CompilationResult(compilation, result)\n}\n\nfun StringSubject.hasSourceEqualTo(sourceContents: String) = isEqualTo(sourceContents.trimIndent())\n\nobject CommonSources {\n  // generated code always includes public and Unit\n  @Suppress(\"RedundantVisibilityModifier\", \"RedundantUnitReturnType\")\n  @Language(\"kotlin\")\n  const val simpleAppGlideModule =\n    \"\"\"\npackage com.bumptech.glide\n\nimport AppModule\nimport android.content.Context\nimport kotlin.Boolean\nimport kotlin.Suppress\nimport kotlin.Unit\n\ninternal class GeneratedAppGlideModuleImpl(\n  @Suppress(\"UNUSED_PARAMETER\")\n  context: Context,\n) : GeneratedAppGlideModule() {\n  private val appGlideModule: AppModule\n  init {\n    appGlideModule = AppModule()\n  }\n\n  public override fun registerComponents(\n    context: Context,\n    glide: Glide,\n    registry: Registry,\n  ): Unit {\n    appGlideModule.registerComponents(context, glide, registry)\n  }\n\n  public override fun applyOptions(context: Context, builder: GlideBuilder): Unit {\n    appGlideModule.applyOptions(context, builder)\n  }\n\n  public override fun isManifestParsingEnabled(): Boolean = false\n}\n\"\"\"\n}\n"
  },
  {
    "path": "annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/test/LibraryGlideModuleTests.kt",
    "content": "package com.bumptech.glide.annotation.ksp.test\n\nimport com.bumptech.glide.annotation.ksp.AppGlideModuleConstants\nimport com.bumptech.glide.annotation.ksp.GlideSymbolProcessorConstants\nimport com.google.common.truth.Truth.assertThat\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode\nimport java.io.FileNotFoundException\nimport kotlin.test.assertFailsWith\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.Assume.assumeTrue\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\nimport org.junit.runners.Parameterized.Parameters\n\n@RunWith(Parameterized::class)\n@OptIn(ExperimentalCompilerApi::class)\nclass LibraryGlideModuleTests(override val sourceType: SourceType) : PerSourceTypeTest {\n\n  companion object {\n    @Parameters(name = \"sourceType = {0}\") @JvmStatic fun data() = SourceType.values()\n  }\n\n  @Test\n  fun compile_withAnnotatedAndValidLibraryGlideModule_succeeds_butDoesNotGenerateGeneratedAppGlideModule() {\n    val kotlinModule =\n      KotlinSourceFile(\n        \"Module.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class Module : LibraryGlideModule()\n        \"\"\",\n      )\n    val javaModule =\n      JavaSourceFile(\n        \"Module.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.LibraryGlideModule;\n          \n          @GlideModule public class Module extends LibraryGlideModule {}\n        \"\"\",\n      )\n\n    compileCurrentSourceType(kotlinModule, javaModule) {\n      assertThat(it.messages).doesNotContainMatch(\"[we]: \\\\[ksp] .*\")\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertFailsWith<FileNotFoundException> { it.generatedAppGlideModuleContents() }\n    }\n  }\n\n  @Test\n  fun compile_withValidLibraryGlideModule_andAppGlideModule_generatesGeneratedAppGlideModule_andCallsBothLibraryAndAppGlideModules() {\n    val kotlinLibraryModule =\n      KotlinSourceFile(\n        \"LibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val javaLibraryModule =\n      JavaSourceFile(\n        \"LibraryModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.LibraryGlideModule;\n          \n          @GlideModule public class LibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class AppModule extends AppGlideModule {\n            public AppModule() {}\n          }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinLibraryModule,\n      javaAppModule,\n      javaLibraryModule,\n    ) {\n      assertThat(it.messages).doesNotContainMatch(\"[we]: \\\\[ksp] .*\")\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithLibraryModule)\n    }\n  }\n\n  @Test\n  fun compile_withValidLibraryGlideModule_andAppGlideModule_ThroughBaseClass_generatesGeneratedAppGlideModule_andCallsBothLibraryAndAppGlideModules() {\n    val kotlinLibraryModule =\n      KotlinSourceFile(\n        \"LibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        class BaseLibraryModule : LibraryGlideModule()\n        @GlideModule class LibraryModule : BaseLibraryModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        class BaseAppModule : AppGlideModule()\n        @GlideModule class AppModule : BaseAppModule()\n        \"\"\",\n      )\n    val javaBaseLibraryModule =\n      JavaSourceFile(\n        \"BaseLibraryModule.java\",\n        \"\"\"\n          import com.bumptech.glide.module.LibraryGlideModule;\n\n          public class BaseLibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaLibraryModule =\n      JavaSourceFile(\n        \"LibraryModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n\n          @GlideModule public class LibraryModule extends BaseLibraryModule {}\n        \"\"\",\n      )\n    val javaBaseAppModule =\n      JavaSourceFile(\n        \"BaseAppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.module.AppGlideModule;\n\n          public class BaseAppModule extends AppGlideModule {\n            public BaseAppModule() {}\n          }\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n\n          @GlideModule public class AppModule extends BaseAppModule {\n            public AppModule() {}\n          }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinLibraryModule,\n      javaBaseAppModule,\n      javaAppModule,\n      javaBaseLibraryModule,\n      javaLibraryModule,\n    ) {\n      assertThat(it.messages).doesNotContainMatch(\"[we]: \\\\[ksp] .*\")\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithLibraryModule)\n    }\n  }\n\n  @Test\n  fun compile_withMultipleLibraryGlideModules_andAppGlideModule_callsAllLibraryGlideModulesFromGeneratedAppGlideModule() {\n    val kotlinLibraryModule1 =\n      KotlinSourceFile(\n        \"LibraryModule1.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule1 : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinLibraryModule2 =\n      KotlinSourceFile(\n        \"LibraryModule2.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule2 : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val javaLibraryModule1 =\n      JavaSourceFile(\n        \"LibraryModule1.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        @GlideModule public class LibraryModule1 extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaLibraryModule2 =\n      JavaSourceFile(\n        \"LibraryModule2.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        @GlideModule public class LibraryModule2 extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.AppGlideModule;\n        \n        @GlideModule public class AppModule extends AppGlideModule {\n          public AppModule() {}\n        }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinLibraryModule1,\n      kotlinLibraryModule2,\n      javaAppModule,\n      javaLibraryModule1,\n      javaLibraryModule2,\n    ) {\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithMultipleLibraryModules)\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n    }\n  }\n\n  @Test\n  fun compile_withTheSameLibraryGlideModuleInMultipleFiles_andAnAppGlideModule_generatesGeneratedAppGlideModuleThatCallsTheLibraryGlideModuleOnce() {\n    // Kotlin seems fine with multiple identical classes. For Java this is compile time error\n    // already, so we don't have to handle it.\n    assumeTrue(sourceType == SourceType.KOTLIN)\n    val kotlinLibraryModule1 =\n      KotlinSourceFile(\n        \"LibraryModule1.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinLibraryModule2 =\n      KotlinSourceFile(\n        \"LibraryModule2.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\",\n      )\n\n    compileCurrentSourceType(kotlinAppModule, kotlinLibraryModule1, kotlinLibraryModule2) {\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithLibraryModule)\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n      assertThat(it.messages)\n        .contains(\n          GlideSymbolProcessorConstants.DUPLICATE_LIBRARY_MODULE_ERROR.format(\"[LibraryModule]\")\n        )\n    }\n  }\n\n  @Test\n  fun compile_withLibraryGlideModulesWithDifferentPackages_butSameName_andAppGlideModule_callsEachLibraryGlideModuleOnceFromGeneratedAppGlideModule() {\n    // TODO(judds): The two java classes don't compile when run by the annotation processor, which\n    //  means we can't really test this case for java code. Fix compilation issue and re-enable this\n    //  test for Java code.\n    assumeTrue(sourceType == SourceType.KOTLIN)\n    val kotlinLibraryModule1 =\n      KotlinSourceFile(\n        \"LibraryModule1.kt\",\n        \"\"\"\n        package first_package\n\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinLibraryModule2 =\n      KotlinSourceFile(\n        \"LibraryModule2.kt\",\n        \"\"\"\n        package second_package\n\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val javaLibraryModule1 =\n      JavaSourceFile(\n        \"LibraryModule1.java\",\n        \"\"\"\n        package first_package;\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        public class LibraryModule1 {\n          @GlideModule public static final class LibraryModule extends LibraryGlideModule {}\n        }\n        \"\"\",\n      )\n    val javaLibraryModule2 =\n      JavaSourceFile(\n        \"LibraryModule2.java\",\n        \"\"\"\n        package second_package;\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        public class LibraryModule2 {\n          @GlideModule public static final class LibraryModule extends LibraryGlideModule {}\n        }\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.AppGlideModule;\n        \n        @GlideModule public class AppModule extends AppGlideModule {\n          public AppModule() {}\n        }\n        \"\"\",\n      )\n\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinLibraryModule1,\n      kotlinLibraryModule2,\n      javaAppModule,\n      javaLibraryModule1,\n      javaLibraryModule2,\n    ) {\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithPackagePrefixedLibraryModules)\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n    }\n  }\n\n  @Test\n  fun compile_withLibraryModuleInExcludes_producesGeneratedAppGlideModuleThatDoesNotCallExcludedLibraryModule() {\n    val kotlinLibraryModule1 =\n      KotlinSourceFile(\n        \"LibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinLibraryModule2 =\n      KotlinSourceFile(\n        \"ExcludedLibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class ExcludedLibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule \n        @Excludes(ExcludedLibraryModule::class) \n        class AppModule : AppGlideModule()\n        \"\"\",\n      )\n\n    val javaLibraryModule1 =\n      JavaSourceFile(\n        \"LibraryModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        @GlideModule\n        public class LibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaLibraryModule2 =\n      JavaSourceFile(\n        \"ExcludedLibraryModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        @GlideModule\n        public class ExcludedLibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes;\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.AppGlideModule;\n        \n        @GlideModule \n        @Excludes(ExcludedLibraryModule.class)\n        public class AppModule extends AppGlideModule {\n          public AppModule() {}\n        }\n        \"\"\",\n      )\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinLibraryModule1,\n      kotlinLibraryModule2,\n      javaAppModule,\n      javaLibraryModule1,\n      javaLibraryModule2,\n    ) {\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithLibraryModule)\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n    }\n  }\n\n  @Test\n  fun compile_withMultipleLibraryModulesInExcludes_producesGeneratedAppGlideModuleThatDoesNotCallExcludedLibraryModules() {\n    val kotlinLibraryModule1 =\n      KotlinSourceFile(\n        \"LibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinLibraryModule2 =\n      KotlinSourceFile(\n        \"ExcludedLibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class ExcludedLibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule \n        @Excludes(LibraryModule::class, ExcludedLibraryModule::class) \n        class AppModule : AppGlideModule()\n        \"\"\",\n      )\n\n    val javaLibraryModule1 =\n      JavaSourceFile(\n        \"LibraryModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        @GlideModule\n        public class LibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaLibraryModule2 =\n      JavaSourceFile(\n        \"ExcludedLibraryModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.LibraryGlideModule;\n        \n        @GlideModule\n        public class ExcludedLibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes;\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.AppGlideModule;\n        \n        @GlideModule \n        @Excludes({LibraryModule.class, ExcludedLibraryModule.class})\n        public class AppModule extends AppGlideModule {\n          public AppModule() {}\n        }\n        \"\"\",\n      )\n    compileCurrentSourceType(\n      kotlinAppModule,\n      kotlinLibraryModule1,\n      kotlinLibraryModule2,\n      javaAppModule,\n      javaLibraryModule1,\n      javaLibraryModule2,\n    ) {\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(CommonSources.simpleAppGlideModule)\n      assertThat(it.exitCode).isEqualTo(ExitCode.OK)\n    }\n  }\n\n  @Test\n  fun compile_withAppModuleWithEmptyExcludes_fails() {\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule \n        @Excludes\n        class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes;\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.AppGlideModule;\n        \n        @GlideModule \n        @Excludes\n        public class AppModule extends AppGlideModule {\n          public AppModule() {}\n        }\n        \"\"\",\n      )\n    compileCurrentSourceType(kotlinAppModule, javaAppModule) {\n      assertThat(it.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)\n      assertThat(it.messages)\n        .contains(AppGlideModuleConstants.INVALID_EXCLUDES_ANNOTATION_MESSAGE.format(\"AppModule\"))\n    }\n  }\n\n  @Test\n  fun compile_withAppModuleWithExcludes_pointingToAppModules_fails() {\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        class SomeOtherAppModule: AppGlideModule()\n\n        @GlideModule \n        @Excludes(SomeOtherAppModule::class)\n        class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val otherJavaAppModule =\n      JavaSourceFile(\n        \"SomeOtherAppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.module.AppGlideModule;\n        \n        public class SomeOtherAppModule extends AppGlideModule {\n          public SomeOtherAppModule() {}\n        }\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n        import com.bumptech.glide.annotation.Excludes;\n        import com.bumptech.glide.annotation.GlideModule;\n        import com.bumptech.glide.module.AppGlideModule;\n        \n        @GlideModule \n        @Excludes(SomeOtherAppModule.class)\n        public class AppModule extends AppGlideModule {\n          public AppModule() {}\n        }\n        \"\"\",\n      )\n    compileCurrentSourceType(kotlinAppModule, otherJavaAppModule, javaAppModule) {\n      assertThat(it.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)\n      assertThat(it.messages)\n        .contains(AppGlideModuleConstants.INVALID_EXCLUDES_ANNOTATION_MESSAGE.format(\"AppModule\"))\n    }\n  }\n\n  @Test\n  fun compile_withLibraryGlideModule_compiledSeparately_includesLibraryGlideModule_2() {\n    val kotlinLibraryModule =\n      KotlinSourceFile(\n        \"LibraryModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.LibraryGlideModule\n\n        @GlideModule class LibraryModule : LibraryGlideModule()\n        \"\"\",\n      )\n    val javaLibraryModule =\n      JavaSourceFile(\n        \"LibraryModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.LibraryGlideModule;\n          \n          @GlideModule public class LibraryModule extends LibraryGlideModule {}\n        \"\"\",\n      )\n\n    val libraryCompilationResult = compileCurrentSourceType(kotlinLibraryModule, javaLibraryModule)\n\n    val kotlinAppModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\",\n      )\n    val javaAppModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class AppModule extends AppGlideModule {\n            public AppModule() {}\n          }\n        \"\"\",\n      )\n\n    val generatedLibrarySources =\n      libraryCompilationResult.allGeneratedFiles().map { GeneratedSourceFile(it, sourceType) }\n\n    compileCurrentSourceType(\n      *(listOf(kotlinAppModule, javaAppModule) + generatedLibrarySources).toTypedArray()\n    ) {\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(appGlideModuleWithLibraryModule)\n    }\n  }\n}\n\n// generated code always includes public and Unit\n@Suppress(\"RedundantVisibilityModifier\", \"RedundantUnitReturnType\")\n@Language(\"kotlin\")\nconst val appGlideModuleWithPackagePrefixedLibraryModules =\n  \"\"\"\npackage com.bumptech.glide\n\nimport AppModule\nimport android.content.Context\nimport first_package.LibraryModule\nimport kotlin.Boolean\nimport kotlin.Suppress\nimport kotlin.Unit\n\ninternal class GeneratedAppGlideModuleImpl(\n  @Suppress(\"UNUSED_PARAMETER\")\n  context: Context,\n) : GeneratedAppGlideModule() {\n  private val appGlideModule: AppModule\n  init {\n    appGlideModule = AppModule()\n  }\n\n  public override fun registerComponents(\n    context: Context,\n    glide: Glide,\n    registry: Registry,\n  ): Unit {\n    LibraryModule().registerComponents(context, glide, registry)\n    second_package.LibraryModule().registerComponents(context, glide, registry)\n    appGlideModule.registerComponents(context, glide, registry)\n  }\n\n  public override fun applyOptions(context: Context, builder: GlideBuilder): Unit {\n    appGlideModule.applyOptions(context, builder)\n  }\n\n  public override fun isManifestParsingEnabled(): Boolean = false\n}\n\"\"\"\n\n// generated code always includes public and Unit\n@Suppress(\"RedundantVisibilityModifier\", \"RedundantUnitReturnType\")\n@Language(\"kotlin\")\nconst val appGlideModuleWithLibraryModule =\n  \"\"\"\npackage com.bumptech.glide\n\nimport AppModule\nimport LibraryModule\nimport android.content.Context\nimport kotlin.Boolean\nimport kotlin.Suppress\nimport kotlin.Unit\n\ninternal class GeneratedAppGlideModuleImpl(\n  @Suppress(\"UNUSED_PARAMETER\")\n  context: Context,\n) : GeneratedAppGlideModule() {\n  private val appGlideModule: AppModule\n  init {\n    appGlideModule = AppModule()\n  }\n\n  public override fun registerComponents(\n    context: Context,\n    glide: Glide,\n    registry: Registry,\n  ): Unit {\n    LibraryModule().registerComponents(context, glide, registry)\n    appGlideModule.registerComponents(context, glide, registry)\n  }\n\n  public override fun applyOptions(context: Context, builder: GlideBuilder): Unit {\n    appGlideModule.applyOptions(context, builder)\n  }\n\n  public override fun isManifestParsingEnabled(): Boolean = false\n}\n\"\"\"\n\n// generated code always includes public and Unit\n@Suppress(\"RedundantVisibilityModifier\", \"RedundantUnitReturnType\")\n@Language(\"kotlin\")\nconst val appGlideModuleWithMultipleLibraryModules =\n  \"\"\"\npackage com.bumptech.glide\n\nimport AppModule\nimport LibraryModule1\nimport LibraryModule2\nimport android.content.Context\nimport kotlin.Boolean\nimport kotlin.Suppress\nimport kotlin.Unit\n\ninternal class GeneratedAppGlideModuleImpl(\n  @Suppress(\"UNUSED_PARAMETER\")\n  context: Context,\n) : GeneratedAppGlideModule() {\n  private val appGlideModule: AppModule\n  init {\n    appGlideModule = AppModule()\n  }\n\n  public override fun registerComponents(\n    context: Context,\n    glide: Glide,\n    registry: Registry,\n  ): Unit {\n    LibraryModule1().registerComponents(context, glide, registry)\n    LibraryModule2().registerComponents(context, glide, registry)\n    appGlideModule.registerComponents(context, glide, registry)\n  }\n\n  public override fun applyOptions(context: Context, builder: GlideBuilder): Unit {\n    appGlideModule.applyOptions(context, builder)\n  }\n\n  public override fun isManifestParsingEnabled(): Boolean = false\n}\n\"\"\"\n"
  },
  {
    "path": "annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/test/OnlyAppGlideModuleTests.kt",
    "content": "package com.bumptech.glide.annotation.ksp.test\n\nimport com.bumptech.glide.annotation.ksp.AppGlideModuleConstants\nimport com.bumptech.glide.annotation.ksp.GlideSymbolProcessorConstants\nimport com.google.common.truth.Truth.assertThat\nimport com.tschuchort.compiletesting.KotlinCompilation\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\n\n@RunWith(Parameterized::class)\n@OptIn(ExperimentalCompilerApi::class)\nclass OnlyAppGlideModuleTests(override val sourceType: SourceType) : PerSourceTypeTest {\n\n  companion object {\n    @Parameterized.Parameters(name = \"sourceType = {0}\") @JvmStatic fun data() = SourceType.values()\n  }\n\n  @Test\n  fun compile_withGlideModuleOnNonLibraryClass_fails() {\n    val kotlinSource =\n      KotlinSourceFile(\n        \"Something.kt\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule\n          @GlideModule class Something \n        \"\"\"\n      )\n\n    val javaSource =\n      JavaSourceFile(\n        \"Something.java\",\n        \"\"\"\n        package test;\n        \n        import com.bumptech.glide.annotation.GlideModule;\n        @GlideModule\n        public class Something {}\n        \"\"\"\n      )\n\n    compileCurrentSourceType(kotlinSource, javaSource) {\n      assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)\n      assertThat(it.messages)\n        .containsMatch(\n          GlideSymbolProcessorConstants.INVALID_ANNOTATED_CLASS.format(\".*/Something.*\")\n        )\n    }\n  }\n\n  @Test\n  fun compile_withGlideModuleOnValidAppGlideModule_generatedGeneratedAppGlideModule() {\n    val kotlinModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule : AppGlideModule()\n        \"\"\"\n      )\n    val javaModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class AppModule extends AppGlideModule {}\n        \"\"\"\n          .trimIndent()\n      )\n\n    compileCurrentSourceType(kotlinModule, javaModule) {\n      assertThat(it.generatedAppGlideModuleContents())\n        .hasSourceEqualTo(CommonSources.simpleAppGlideModule)\n      assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)\n    }\n  }\n\n  @Test\n  fun compile_withGlideModuleOnValidAppGlideModuleThroughBaseClass_generatedGeneratedAppGlideModule() {\n    val kotlinModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n      import com.bumptech.glide.annotation.GlideModule\n      import com.bumptech.glide.module.AppGlideModule\n      class BaseAppModule : AppGlideModule()\n        @GlideModule class AppModule : BaseAppModule()\n        \"\"\"\n      )\n    val javaBaseAppModule =\n      JavaSourceFile(\n        \"BaseAppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.module.AppGlideModule;\n\n          public class BaseAppModule extends AppGlideModule {}\n        \"\"\"\n                .trimIndent()\n      )\n    val javaModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n\n          @GlideModule public class AppModule extends BaseAppModule {}\n        \"\"\"\n                .trimIndent()\n      )\n\n    compileCurrentSourceType(kotlinModule, javaBaseAppModule, javaModule) {\n      assertThat(it.generatedAppGlideModuleContents())\n          .hasSourceEqualTo(CommonSources.simpleAppGlideModule)\n      assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)\n    }\n  }\n\n  @Test\n  fun compile_withAppGlideModuleConstructorAcceptingOnlyContext_generatesGeneratedAppGlideModule() {\n    val kotlinModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import android.content.Context\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule(context: Context) : AppGlideModule()\n        \"\"\"\n      )\n\n    val javaModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import android.content.Context;\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class AppModule extends AppGlideModule {\n            public AppModule(Context context) {}\n          }\n        \"\"\"\n      )\n\n    compileCurrentSourceType(kotlinModule, javaModule) {\n      assertThat(it.generatedAppGlideModuleContents()).hasSourceEqualTo(appGlideModuleWithContext)\n      assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)\n    }\n  }\n\n  @Test\n  fun compile_withAppGlideModuleConstructorRequiringOtherThanContext_fails() {\n    val kotlinModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule(value: Int) : AppGlideModule()\n        \"\"\"\n      )\n    val javaModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class AppModule extends AppGlideModule {\n            public AppModule(Integer value) {}\n          }\n        \"\"\"\n      )\n\n    compileCurrentSourceType(kotlinModule, javaModule) {\n      assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)\n      assertThat(it.messages).contains(AppGlideModuleConstants.INVALID_MODULE_MESSAGE)\n    }\n  }\n\n  @Test\n  fun compile_withAppGlideModuleConstructorRequiringMultipleArguments_fails() {\n    val kotlinModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import android.content.Context\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule(value: Context, otherValue: Int) : AppGlideModule()\n        \"\"\"\n      )\n    val javaModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import android.content.Context;\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class AppModule extends AppGlideModule {\n            public AppModule(Context value, int otherValue) {}\n          }\n        \"\"\"\n      )\n\n    compileCurrentSourceType(kotlinModule, javaModule) {\n      assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)\n      assertThat(it.messages).contains(AppGlideModuleConstants.INVALID_MODULE_MESSAGE)\n    }\n  }\n\n  // This is quite weird, we could probably pretty reasonably just assert that this doesn't happen.\n  @Test\n  fun compile_withAppGlideModuleWithOneEmptyConstructor_andOneContextOnlyConstructor_usesTheContextOnlyConstructor() {\n    val kotlinModule =\n      KotlinSourceFile(\n        \"AppModule.kt\",\n        \"\"\"\n        import android.content.Context\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class AppModule(context: Context?) : AppGlideModule() {\n          constructor() : this(null)\n        }\n        \n        \"\"\"\n      )\n    val javaModule =\n      JavaSourceFile(\n        \"AppModule.java\",\n        \"\"\"\n          import android.content.Context;\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          import javax.annotation.Nullable;\n          \n          @GlideModule public class AppModule extends AppGlideModule {\n            public AppModule() {}\n            public AppModule(@Nullable Context context) {}\n          }\n        \"\"\"\n      )\n\n    compileCurrentSourceType(kotlinModule, javaModule) {\n      assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)\n      assertThat(it.generatedAppGlideModuleContents()).hasSourceEqualTo(appGlideModuleWithContext)\n    }\n  }\n\n  @Test\n  fun compile_withMultipleAppGlideModules_fails() {\n    val firstKtModule =\n      KotlinSourceFile(\n        \"Module1.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class Module1 : AppGlideModule()\n        \"\"\"\n      )\n\n    val secondKtModule =\n      KotlinSourceFile(\n        \"Module2.kt\",\n        \"\"\"\n        import com.bumptech.glide.annotation.GlideModule\n        import com.bumptech.glide.module.AppGlideModule\n\n        @GlideModule class Module2 : AppGlideModule()\n        \"\"\"\n      )\n\n    val firstJavaModule =\n      JavaSourceFile(\n        \"Module1.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class Module1 extends AppGlideModule {\n            public Module1() {}\n          }\n        \"\"\"\n      )\n\n    val secondJavaModule =\n      JavaSourceFile(\n        \"Module2.java\",\n        \"\"\"\n          import com.bumptech.glide.annotation.GlideModule;\n          import com.bumptech.glide.module.AppGlideModule;\n          \n          @GlideModule public class Module2 extends AppGlideModule {\n            public Module2() {}\n          }\n        \"\"\"\n      )\n\n    compileCurrentSourceType(firstKtModule, secondKtModule, firstJavaModule, secondJavaModule) {\n      assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)\n      assertThat(it.messages)\n        .contains(\n          GlideSymbolProcessorConstants.SINGLE_APP_MODULE_ERROR.format(\"[Module1, Module2]\")\n        )\n    }\n  }\n}\n\n@Language(\"kotlin\")\nconst val appGlideModuleWithContext =\n  \"\"\"\npackage com.bumptech.glide\n\nimport AppModule\nimport android.content.Context\nimport kotlin.Boolean\nimport kotlin.Unit\n\ninternal class GeneratedAppGlideModuleImpl(\n  context: Context,\n) : GeneratedAppGlideModule() {\n  private val appGlideModule: AppModule\n  init {\n    appGlideModule = AppModule(context)\n  }\n\n  public override fun registerComponents(\n    context: Context,\n    glide: Glide,\n    registry: Registry,\n  ): Unit {\n    appGlideModule.registerComponents(context, glide, registry)\n  }\n\n  public override fun applyOptions(context: Context, builder: GlideBuilder): Unit {\n    appGlideModule.applyOptions(context, builder)\n  }\n\n  public override fun isManifestParsingEnabled(): Boolean = false\n}\n\"\"\"\n"
  },
  {
    "path": "annotation/src/main/java/com/bumptech/glide/annotation/Excludes.java",
    "content": "package com.bumptech.glide.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Specifies a set of GlideModule and/or LibraryGlideModule classes that should be excluded from an\n * application.\n *\n * <p>Used only on AppGlideModules. Adding this annotation to other classes will have no affect.\n *\n * <p>Cannot be used to exclude AppGlideModules (there must be at most one per Application anyway).\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Excludes {\n  Class<?>[] value();\n}\n"
  },
  {
    "path": "annotation/src/main/java/com/bumptech/glide/annotation/GlideExtension.java",
    "content": "package com.bumptech.glide.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Indicate a class that extends Glide's public API.\n *\n * @see GlideOption\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.CLASS)\npublic @interface GlideExtension {}\n"
  },
  {
    "path": "annotation/src/main/java/com/bumptech/glide/annotation/GlideModule.java",
    "content": "package com.bumptech.glide.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Identifies AppGlideModules and LibraryGlideModules for Glide's annotation processor to merge at\n * compile time.\n *\n * <p>Replaces {@code <meta-data />} tags in AndroidManifest.xml.\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.CLASS)\npublic @interface GlideModule {\n  /**\n   * Returns the name of the class that will be used as a replacement for {@code\n   * com.bumptech.glide.Glide} in Applications that depend on Glide's generated code.\n   */\n  String glideName() default \"GlideApp\";\n}\n"
  },
  {
    "path": "annotation/src/main/java/com/bumptech/glide/annotation/GlideOption.java",
    "content": "package com.bumptech.glide.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Identifies methods in {@link GlideExtension} annotated classes that extend {@code\n * com.bumptech.glide.request.RequestOptions}.\n *\n * <p>All annotated methods will be added to a single {@code\n * com.bumptech.glide.request.RequestOptions} implementation generated per application. Overlapping\n * method names in different extensions may cause errors at compile time.\n *\n * <p>Static equivalents of annotated methods will also be generated.\n *\n * <p>Methods with this annotation will only be found if they belong to classes annotated with\n * {@link GlideExtension}.\n *\n * <p>The preferred way of writing extension methods returns the provided {@code\n * com.bumptech.glide.request.RequestOptions} object with one or more methods called on it. You must\n * not return a newly instantiated {@code com.bumptech.glide.request.RequestOptions} object as doing\n * so my cause a {@code ClassCastException} at runtime. Calling either {@code\n * com.bumptech.glide.request.RequestOptions#autoClone()} or {@code\n * com.bumptech.glide.request.RequestOptions#lock()} is safe, but unnecessary and should typically\n * be avoided. The preferred style looks like:\n *\n * <pre>{@code\n * {@link @}GlideExtension\n * public class MyExtension {\n *   private MyExtension() {}\n *\n *   {@literal @}GlideOption\n *   public static RequestOptions myOption(RequestOptions options) {\n *     return options\n *         .optionOne()\n *         .optionTwo();\n *   }\n * }\n * }</pre>\n *\n * <p>The deprecated way of writing extension methods is simply a static void method. The {@code\n * com.bumptech.glide.request.RequestOptions} object is cloned before it is passed to this method to\n * avoid an option method returning a new instance, but using methods like {@code\n * com.bumptech.glide.request.RequestOptions#clone()} or {@code\n * com.bumptech.glide.request.RequestOptions#autoClone()} can result in options applied in the\n * method being silently ignored. Prefer the new style whenever possible.\n *\n * <pre>{@code\n * {@literal @}GlideExtension\n * public class MyExtension {\n *   private MyExtension() {}\n *\n *   // Deprecated! Use the new style of GlideOption extensions instead.\n *   {@literal @}GlideOption\n *   public static void myOption(RequestOptions options) {\n *     options\n *         .optionOne()\n *         .optionTwo();\n *   }\n * }\n * }</pre>\n */\n@Target(ElementType.METHOD)\n// Needs to be parsed from class files in JAR.\n@Retention(RetentionPolicy.CLASS)\npublic @interface GlideOption {\n  /** Does not intend to override a method in a super class. */\n  int OVERRIDE_NONE = 0;\n\n  /** Expects to call super and then add additional functionality to an overridden method. */\n  int OVERRIDE_EXTEND = 1;\n\n  /** Expects to not call super and replace an overridden method. */\n  int OVERRIDE_REPLACE = 2;\n\n  /**\n   * Determines how and whether a generated method should extend a method from it's parent.\n   *\n   * <p>Must be one of {@link #OVERRIDE_NONE}, {@link #OVERRIDE_EXTEND}, {@link #OVERRIDE_REPLACE}.\n   *\n   * <p>The extended method is determined by String and argument matching against methods in the\n   * extended class. If {@link #OVERRIDE_NONE} is used and the method and arguments match a method\n   * in the extended class, a compile time error will result. Similarly if any other override type\n   * is used and no method/arguments in the extended class match, a compile time error will result.\n   */\n  int override() default OVERRIDE_NONE;\n\n  /**\n   * Sets the name for the generated static version of this method.\n   *\n   * <p>If this value is not set, the static method name is just the original method name with \"Of\"\n   * appended.\n   */\n  String staticMethodName() default \"\";\n\n  /**\n   * {@code true} to indicate that it's safe to statically memoize the result of this method using\n   * {@code com.bumptech.glide.request.RequestOptions#autoClone()}.\n   *\n   * <p>This method should only be used for no-arg methods where there's only a single possible\n   * value.\n   *\n   * <p>Memoization can save object allocations for frequently used options.\n   */\n  boolean memoizeStaticMethod() default false;\n\n  /**\n   * {@code true} to prevent a static builder method from being generated.\n   *\n   * <p>By default static methods are generated for all methods annotated with {@link GlideOption}.\n   * These static factory methods allow for a cleaner API when used with {@code\n   * com.bumptech.glide.RequestBuilder#apply}. The static factory method by default simply creates a\n   * new {@code com.bumptech.glide.request.RequestOptions} object, calls the instance version of the\n   * method on it and returns it. For example:\n   *\n   * <pre>\n   * <code>\n   * public static GlideOptions noAnimation() {\n   *   return new GlideOptions().dontAnimate();\n   * }\n   * </code>\n   * </pre>\n   *\n   * @see #memoizeStaticMethod()\n   * @see #staticMethodName()\n   */\n  boolean skipStaticMethod() default false;\n}\n"
  },
  {
    "path": "annotation/src/main/java/com/bumptech/glide/annotation/GlideType.java",
    "content": "package com.bumptech.glide.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Identifies methods in {@link GlideExtension} annotated classes that extend {@code\n * com.bumptech.glide.RequestManager}.\n *\n * <p>If one or more method is found with this annotation, an additional API entry point that\n * exposes a generated {@code com.bumptech.glide.RequestManager} subclass will be created. The\n * generated API entry point acts as a drop in replacement for Glide. Glide.with(fragment) becomes\n * GlideApp.with(fragment). Although the Glide.with variant will still be available, only the new\n * API entry point will provide access to these additional methods.\n *\n * <p>The name of the API entry point created when one of these methods is found can be controlled\n * by {@link GlideModule#glideName()}.\n *\n * <p>Methods with this annotation will only be found if they are contained in a class annotated\n * with {@link GlideExtension}.\n *\n * <p>Methods annotated with GlideType must have a single parameter. The type of the single\n * parameter must be {@code com.bumptech.glide.RequestBuilder}, with a type matching the value of\n * {@link #value()}.\n *\n * <p>Compilation will fail if a method annotated with this method is identical to a method in\n * {@code com.bumptech.glide.RequestManager}\n */\n@Target(ElementType.METHOD)\n// Needs to be parsed from class files in JAR.\n@Retention(RetentionPolicy.CLASS)\npublic @interface GlideType {\n\n  /**\n   * A Resource class name, like GifDrawable.class, Bitmap.class etc.\n   *\n   * <p>Must match the type of the {@code com.bumptech.glide.RequestBuilder} parameter in the\n   * annotated method.\n   */\n  Class<?> value();\n}\n"
  },
  {
    "path": "annotation/src/main/java/com/bumptech/glide/annotation/compiler/Index.java",
    "content": "package com.bumptech.glide.annotation.compiler;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Used to retrieve LibraryGlideModule and GlideExtension classes in our annotation processor from\n * libraries and applications.\n *\n * <p>Part of the internals of Glide's annotation processor and not for public use.\n */\n@Target(ElementType.TYPE)\n// Needs to be parsed from class files in JAR.\n@Retention(RetentionPolicy.CLASS)\n@interface Index {\n  String[] modules() default {};\n\n  String[] extensions() default {};\n}\n"
  },
  {
    "path": "annotation/src/main/java/com/bumptech/glide/annotation/ksp/Index.java",
    "content": "package com.bumptech.glide.annotation.ksp;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Used to retrieve LibraryGlideModule and GlideExtension classes in our annotation processor from\n * libraries and applications.\n *\n * <p>Part of the internals of Glide's annotation processor and not for public use.\n */\n@Target(ElementType.TYPE)\n// Needs to be parsed from class files in JAR.\n@Retention(RetentionPolicy.CLASS)\n@interface Index {\n  String[] modules() default {};\n}\n"
  },
  {
    "path": "benchmark/benchmark-proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\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\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-dontobfuscate\n\n-ignorewarnings\n\n-keepattributes *Annotation*\n\n-dontnote junit.framework.**\n-dontnote junit.runner.**\n\n-dontwarn androidx.test.**\n-dontwarn org.junit.**\n-dontwarn org.hamcrest.**\n-dontwarn com.squareup.javawriter.JavaWriter\n\n-keepclasseswithmembers @org.junit.runner.RunWith public class *"
  },
  {
    "path": "benchmark/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n    id(\"androidx.benchmark\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.benchmark\"\n    compileSdk = 34\n    buildToolsVersion = \"34.0.0\"\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n\n    defaultConfig {\n        minSdk = 19\n\n        testInstrumentationRunner = \"androidx.benchmark.junit4.AndroidBenchmarkRunner\"\n        multiDexEnabled = true\n    }\n\n    buildTypes {\n        getByName(\"debug\") {\n            isMinifyEnabled = true\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"benchmark-proguard-rules.pro\"\n            )\n        }\n    }\n}\n\ndependencies {\n    implementation(libs.androidx.multidex)\n\n    androidTestImplementation(libs.androidx.test.runner)\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.junit)\n\n    androidTestImplementation(project(\":library\"))\n    androidTestImplementation(project(\":testutil\"))\n    androidTestImplementation(libs.androidx.benchmark.junit)\n    androidTestImplementation(libs.guava)\n}"
  },
  {
    "path": "benchmark/gradle.properties",
    "content": "android.enableAdditionalTestOutput=true\n"
  },
  {
    "path": "benchmark/src/androidTest/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\">\n\n  <!--\n      Important: disable debugging for accurate performance results\n\n      In a com.android.library project, this flag must be disabled from this\n      manifest, as it is not possible to override this flag from Gradle.\n    -->\n  <application\n    android:debuggable=\"false\"\n    tools:ignore=\"HardcodedDebugMode\"\n    tools:replace=\"android:debuggable\" >\n  </application>\n</manifest>\n"
  },
  {
    "path": "benchmark/src/androidTest/java/com/bumptech/glide/benchmark/BenchmarkData.java",
    "content": "package com.bumptech.glide.benchmark;\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.benchmark.GlideBenchmarkRule.AfterStep;\nimport com.bumptech.glide.benchmark.GlideBenchmarkRule.BeforeStep;\nimport com.bumptech.glide.benchmark.GlideBenchmarkRule.LoadStep;\nimport com.bumptech.glide.benchmark.data.DataOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.ByteArrayBufferOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.ParcelFileDescriptorOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.StreamOpener;\nimport com.bumptech.glide.testutil.MockModelLoader;\nimport java.io.IOException;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class BenchmarkData {\n  private final int smallResourceId = R.raw.small;\n  private final int hugeHeaderResourceId = R.raw.huge_header;\n  @Rule public final GlideBenchmarkRule glideBenchmarkRule = new GlideBenchmarkRule();\n\n  @Test\n  public void smallAsStream() throws Exception {\n    benchmarkData(new StreamOpener(), smallResourceId);\n  }\n\n  @Test\n  public void hugeHeaderAsStream() throws Exception {\n    benchmarkData(new StreamOpener(), hugeHeaderResourceId);\n  }\n\n  @Test\n  public void smallAsByteArrayBuffer() throws Exception {\n    benchmarkData(new ByteArrayBufferOpener(), smallResourceId);\n  }\n\n  @Test\n  public void hugeHeaderAsByteArrayBuffer() throws Exception {\n    benchmarkData(new ByteArrayBufferOpener(), hugeHeaderResourceId);\n  }\n\n  @Test\n  public void smallAsFileDescriptor() throws Exception {\n    benchmarkData(new ParcelFileDescriptorOpener(), smallResourceId);\n  }\n\n  @Test\n  public void hugeHeaderAsFileDescriptor() throws Exception {\n    benchmarkData(new ParcelFileDescriptorOpener(), hugeHeaderResourceId);\n  }\n\n  static final class ModelAndData<DataT> {\n    private final Object model;\n    private final DataT data;\n\n    ModelAndData(Object model, DataT data) {\n      this.model = model;\n      this.data = data;\n    }\n  }\n\n  private <T> void benchmarkData(final DataOpener<T> opener, final int resourceId)\n      throws Exception {\n    glideBenchmarkRule.runBenchmark(\n        new BeforeStep<ModelAndData<T>>() {\n          @Override\n          public ModelAndData<T> act() throws IOException {\n            FakeModel fakeModel = new FakeModel();\n            T data = opener.acquire(resourceId);\n            MockModelLoader.mock(fakeModel, data);\n            return new ModelAndData<T>(fakeModel, data);\n          }\n        },\n        new LoadStep<ModelAndData<T>>() {\n          @Override\n          public Object getModel(ModelAndData<T> beforeData) {\n            return beforeData.model;\n          }\n        },\n        new AfterStep<ModelAndData<T>>() {\n          @Override\n          public void act(ModelAndData<T> beforeData) throws IOException {\n            opener.close(beforeData.data);\n          }\n        });\n  }\n\n  private static final class FakeModel {}\n}\n"
  },
  {
    "path": "benchmark/src/androidTest/java/com/bumptech/glide/benchmark/BenchmarkFromCache.java",
    "content": "package com.bumptech.glide.benchmark;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport androidx.benchmark.BenchmarkState;\nimport androidx.benchmark.junit4.BenchmarkRule;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.request.FutureTarget;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.google.common.base.Preconditions;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/** Simulate loading a file from Glide's cache as a thumbnail in various sizes. */\n@RunWith(AndroidJUnit4.class)\npublic class BenchmarkFromCache {\n  private final ConcurrencyHelper concurrencyHelper = new ConcurrencyHelper();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  @Rule public final BenchmarkRule benchmarkRule = new BenchmarkRule();\n\n  private final Application app = ApplicationProvider.getApplicationContext();\n\n  @Test\n  public void pixel3a_portrait_original() throws Exception {\n    runBenchmark(R.raw.pixel3a_portrait, Target.SIZE_ORIGINAL);\n  }\n\n  @Test\n  public void pixel3a_portrait_large() throws Exception {\n    runBenchmark(R.raw.pixel3a_portrait, 2048);\n  }\n\n  @Test\n  public void pixel3a_portrait_medium() throws Exception {\n    runBenchmark(R.raw.pixel3a_portrait, 1024);\n  }\n\n  @Test\n  public void pixel3a_portrait_small() throws Exception {\n    runBenchmark(R.raw.pixel3a_portrait, 256);\n  }\n\n  @Test\n  public void pixel3a_portrait_tiny() throws Exception {\n    runBenchmark(R.raw.pixel3a_portrait, 50);\n  }\n\n  private void runBenchmark(@RawRes final int resourceId, final int targetSize) throws Exception {\n    BenchmarkState state = benchmarkRule.getState();\n    state.pauseTiming();\n    // Writes to the disk cache happen asynchronously after a request completes. To make sure we're\n    // only timing reads from disk cache, we need to make sure we wait until that async write\n    // finishes. This is a simple, albiet hacky, way to accomplish that.\n    try {\n      while (true) {\n        loadImageWithExpectedDataSource(\n            state, resourceId, targetSize, DataSource.LOCAL, /* isAlreadyPaused= */ true);\n      }\n    } catch (IllegalStateException e) {\n      // Now that we're no longer getting LOCAL as our data source, it's safe to proceed.\n    }\n    state.resumeTiming();\n\n    while (state.keepRunning()) {\n      state.pauseTiming();\n      clearMemoryCache();\n      state.resumeTiming();\n\n      loadImageWithExpectedDataSource(\n          state,\n          resourceId,\n          targetSize,\n          DataSource.RESOURCE_DISK_CACHE,\n          /* isAlreadyPaused= */ false);\n    }\n  }\n\n  private void loadImageWithExpectedDataSource(\n      BenchmarkState state,\n      @RawRes int resourceId,\n      int targetSize,\n      DataSource expectedDataSource,\n      boolean isAlreadyPaused)\n      throws InterruptedException, ExecutionException, TimeoutException {\n    final AtomicReference<DataSource> dataSourceRef = new AtomicReference<>();\n    FutureTarget<Bitmap> target =\n        Glide.with(app)\n            .asBitmap()\n            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n            .skipMemoryCache(true)\n            .override(targetSize)\n            .load(resourceId)\n            .listener(\n                new RequestListener<Bitmap>() {\n                  @Override\n                  public boolean onLoadFailed(\n                      @Nullable GlideException e,\n                      Object model,\n                      @NonNull Target<Bitmap> target,\n                      boolean isFirstResource) {\n                    return false;\n                  }\n\n                  @Override\n                  public boolean onResourceReady(\n                      @NonNull Bitmap resource,\n                      @NonNull Object model,\n                      Target<Bitmap> target,\n                      @NonNull DataSource dataSource,\n                      boolean isFirstResource) {\n                    dataSourceRef.set(dataSource);\n                    return false;\n                  }\n                })\n            .submit();\n    target.get(15, TimeUnit.SECONDS);\n\n    if (!isAlreadyPaused) {\n      state.pauseTiming();\n    }\n\n    Preconditions.checkState(dataSourceRef.get() == expectedDataSource, dataSourceRef.get());\n    Glide.with(app).clear(target);\n\n    if (!isAlreadyPaused) {\n      state.resumeTiming();\n    }\n  }\n\n  private void clearMemoryCache() {\n    concurrencyHelper.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(app).clearMemory();\n          }\n        });\n  }\n}\n"
  },
  {
    "path": "benchmark/src/androidTest/java/com/bumptech/glide/benchmark/BenchmarkMediaStoreData.java",
    "content": "package com.bumptech.glide.benchmark;\n\nimport android.Manifest.permission;\nimport android.app.Application;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport androidx.benchmark.BenchmarkState;\nimport androidx.benchmark.junit4.BenchmarkRule;\nimport androidx.core.content.ContextCompat;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.google.common.base.Preconditions;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.concurrent.Callable;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * Benchmarks that do not involve Glide just to figure out how media store is behaving on a given\n * device.\n */\n@RunWith(AndroidJUnit4.class)\npublic class BenchmarkMediaStoreData {\n  // Pick a media store Uri on the device that you know has lat/lng.\n  private static final Uri MEDIA_STORE_URI = Uri.parse(\"content://media/external/images/media/194\");\n  private final Application app = ApplicationProvider.getApplicationContext();\n  @Rule private final BenchmarkRule benchmarkRule = new BenchmarkRule();\n\n  @Before\n  public void setUp() {\n    benchmarkRule.getState().pauseTiming();\n    Preconditions.checkState(\n        ContextCompat.checkSelfPermission(app, permission.ACCESS_MEDIA_LOCATION)\n            == PackageManager.PERMISSION_GRANTED);\n    benchmarkRule.getState().resumeTiming();\n  }\n\n  @Test\n  public void readCacheFileFully() throws Exception {\n    benchmarkRule.getState().pauseTiming();\n    InputStream is = null;\n    OutputStream os = null;\n    final File file;\n    try {\n      is = app.getContentResolver().openInputStream(MEDIA_STORE_URI);\n      file = File.createTempFile(\"tempBenchmarkModel\", \"jpg\", app.getCacheDir());\n      os = new FileOutputStream(file);\n\n      byte[] buffer = new byte[1024 * 1024];\n      int read;\n      while ((read = is.read(buffer, 0, buffer.length)) != -1) {\n        os.write(buffer, 0, read);\n      }\n      os.close();\n    } finally {\n      if (is != null) {\n        is.close();\n      }\n      if (os != null) {\n        os.close();\n      }\n    }\n    benchmarkRule.getState().resumeTiming();\n\n    BenchmarkState state = benchmarkRule.getState();\n    while (state.keepRunning()) {\n      readFully(\n          new Callable<InputStream>() {\n            @Override\n            public InputStream call() throws Exception {\n              return new FileInputStream(file);\n            }\n          });\n    }\n  }\n\n  @Test\n  public void readMediaStoreFileFully() throws Exception {\n    BenchmarkState state = benchmarkRule.getState();\n    while (state.keepRunning()) {\n      readFully(\n          new Callable<InputStream>() {\n            @Override\n            public InputStream call() throws Exception {\n              return app.getContentResolver().openInputStream(MEDIA_STORE_URI);\n            }\n          });\n    }\n  }\n\n  private void readFully(Callable<InputStream> openInputStream) throws Exception {\n    InputStream is = null;\n    try {\n      is = openInputStream.call();\n      byte[] buffer = new byte[1024 * 1024];\n      while (is.read(buffer, 0, buffer.length) != -1) {\n        // Continue\n      }\n    } finally {\n      if (is != null) {\n        is.close();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "benchmark/src/androidTest/java/com/bumptech/glide/benchmark/BenchmarkModels.java",
    "content": "package com.bumptech.glide.benchmark;\n\nimport android.app.Application;\nimport android.content.ContentResolver;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.MediaStore;\nimport androidx.annotation.RawRes;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.benchmark.GlideBenchmarkRule.AfterStep;\nimport com.bumptech.glide.benchmark.GlideBenchmarkRule.BeforeStep;\nimport com.bumptech.glide.benchmark.data.DataOpener.FileOpener;\nimport com.google.common.base.Preconditions;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * Tests that simulate the complete Glide flow by providing supported Model types directly to Glide.\n *\n * <p>While these benchmarks most directly mimic Glide's current behavior, they have some additional\n * noise due to the large number of steps involved. Other benchmarks in this project may be more\n * targeted and provide more accurate results for smaller tweaks.\n */\n@RunWith(AndroidJUnit4.class)\npublic class BenchmarkModels {\n  private final Application app = ApplicationProvider.getApplicationContext();\n  private final int smallResourceId = R.raw.small;\n  private final int hugeHeaderResourceId = R.raw.huge_header;\n  @Rule public final GlideBenchmarkRule glideBenchmarkRule = new GlideBenchmarkRule();\n\n  @Test\n  public void smallAsCacheFile() throws Exception {\n    benchmarkAsCacheFile(smallResourceId);\n  }\n\n  @Test\n  public void hugeHeaderAsCacheFile() throws Exception {\n    benchmarkAsCacheFile(hugeHeaderResourceId);\n  }\n\n  @Test\n  public void smallAsResourceId() throws Exception {\n    benchmarkModel(smallResourceId);\n  }\n\n  @Test\n  public void hugeHeaderAsResourceId() throws Exception {\n    benchmarkModel(hugeHeaderResourceId);\n  }\n\n  @Test\n  public void smallAsResourceUri() throws Exception {\n    Uri uri = resourceUriFromId(smallResourceId);\n    benchmarkModel(uri);\n  }\n\n  @Test\n  public void hugeHeaderAsResourceUri() throws Exception {\n    Uri uri = resourceUriFromId(hugeHeaderResourceId);\n    benchmarkModel(uri);\n  }\n\n  @Test\n  public void smallAsMediaStoreUri() throws Exception {\n    benchmarkAsMediaStoreUri(smallResourceId);\n  }\n\n  @Test\n  public void hugeHeaderAsMediaStoreUri() throws Exception {\n    benchmarkAsMediaStoreUri(hugeHeaderResourceId);\n  }\n\n  @Test\n  public void pixel3aAsMediaStoreUri() throws Exception {\n    benchmarkAsMediaStoreUri(R.raw.pixel3a_portrait);\n  }\n\n  @Test\n  public void pixel3aExifRotatedAsMediaStoreUri() throws Exception {\n    benchmarkAsMediaStoreUri(R.raw.pixel3a_exif_rotated);\n  }\n\n  @Test\n  public void pixel3aMvimgExifRotatedAsMediaStoreUri() throws Exception {\n    benchmarkAsMediaStoreUri(R.raw.pixel3a_mvimg_exif_rotated);\n  }\n\n  @Test\n  public void smallAsMediaStoreFilepath() throws Exception {\n    benchmarkAsMediaStoreFilepath(smallResourceId);\n  }\n\n  @Test\n  public void pixel3aAsMediaStoreFilepath() throws Exception {\n    benchmarkAsMediaStoreFilepath(R.raw.pixel3a_portrait);\n  }\n\n  @Test\n  public void pixel3aExifRotatedAsMediaStoreFilepath() throws Exception {\n    benchmarkAsMediaStoreFilepath(R.raw.pixel3a_exif_rotated);\n  }\n\n  @Test\n  public void pixel3aMvimgExifRotatedAsMediaStoreFilepath() throws Exception {\n    benchmarkAsMediaStoreFilepath(R.raw.pixel3a_mvimg_exif_rotated);\n  }\n\n  @Test\n  public void hugeHeaderAsMediaStoreFilepath() throws Exception {\n    benchmarkAsMediaStoreFilepath(hugeHeaderResourceId);\n  }\n\n  private Uri resourceUriFromId(@RawRes int resourceId) {\n    glideBenchmarkRule.pauseTiming();\n    try {\n      return new Uri.Builder()\n          .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n          .authority(app.getPackageName())\n          .appendPath(app.getResources().getResourceTypeName(resourceId))\n          .appendPath(app.getResources().getResourceEntryName(resourceId))\n          .build();\n    } finally {\n      glideBenchmarkRule.resumeTiming();\n    }\n  }\n\n  private Uri mediaStoreUriFromId(@RawRes int resourceId) throws IOException {\n    glideBenchmarkRule.pauseTiming();\n    try {\n      Uri mediaStoreUri =\n          app.getContentResolver()\n              .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());\n      InputStream is = null;\n      OutputStream os = null;\n      try {\n        is = app.getResources().openRawResource(resourceId);\n        os = app.getContentResolver().openOutputStream(mediaStoreUri);\n        byte[] buffer = new byte[1024 * 1024];\n        int read;\n        while ((read = is.read(buffer, /* off= */ 0, buffer.length)) != -1) {\n          os.write(buffer, /* off= */ 0, read);\n        }\n        // Make sure we actually write all of the data or fail by throwing immediately.\n        os.close();\n        return mediaStoreUri;\n      } finally {\n        if (is != null) {\n          try {\n            is.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n        if (os != null) {\n          try {\n            os.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n      }\n    } finally {\n      glideBenchmarkRule.resumeTiming();\n    }\n  }\n\n  private void benchmarkAsMediaStoreUri(@RawRes int resourceId) throws Exception {\n    Uri mediaStoreUri = mediaStoreUriFromId(resourceId);\n    try {\n      benchmarkModel(mediaStoreUri);\n    } finally {\n      cleanupMediaStoreUri(mediaStoreUri);\n    }\n  }\n\n  private void cleanupMediaStoreUri(Uri mediaStoreUri) {\n    glideBenchmarkRule.pauseTiming();\n    int result = app.getContentResolver().delete(mediaStoreUri, /* extras= */ null);\n    Preconditions.checkState(result == 1);\n    glideBenchmarkRule.resumeTiming();\n  }\n\n  private void benchmarkAsMediaStoreFilepath(@RawRes int resourceId) throws Exception {\n    Uri mediaStoreUri = mediaStoreUriFromId(resourceId);\n    try {\n      benchmarkModel(getMediaStoreFilepath(mediaStoreUri));\n    } finally {\n      cleanupMediaStoreUri(mediaStoreUri);\n    }\n  }\n\n  private String getMediaStoreFilepath(Uri mediaStoreUri) {\n    glideBenchmarkRule.pauseTiming();\n    String[] projection = new String[] {MediaStore.Images.Media.DATA};\n    Cursor cursor =\n        app.getContentResolver()\n            .query(\n                mediaStoreUri,\n                projection,\n                /* selection= */ null,\n                /* selectionArgs= */ null,\n                /* sortOrder= */ null);\n    try {\n      Preconditions.checkState(cursor.moveToFirst());\n      return cursor.getString(0);\n    } finally {\n      cursor.close();\n      glideBenchmarkRule.resumeTiming();\n    }\n  }\n\n  private void benchmarkAsCacheFile(@RawRes final int resourceId) throws Exception {\n    final FileOpener fileOpener = new FileOpener();\n    glideBenchmarkRule.runBenchmark(\n        new BeforeStep<File>() {\n          @Override\n          public File act() throws IOException {\n            return fileOpener.acquire(resourceId);\n          }\n        },\n        new AfterStep<File>() {\n          @Override\n          public void act(File beforeData) {\n            fileOpener.close(beforeData);\n          }\n        });\n  }\n\n  private void benchmarkModel(final Object model) throws Exception {\n    glideBenchmarkRule.runBenchmark(\n        new BeforeStep<Object>() {\n          @Override\n          public Object act() {\n            return model;\n          }\n        },\n        new AfterStep<Object>() {\n          @Override\n          public void act(Object beforeData) {}\n        });\n  }\n}\n"
  },
  {
    "path": "benchmark/src/androidTest/java/com/bumptech/glide/benchmark/GlideBenchmarkRule.java",
    "content": "package com.bumptech.glide.benchmark;\n\nimport android.content.Context;\nimport androidx.benchmark.BenchmarkState;\nimport androidx.benchmark.junit4.BenchmarkRule;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.util.concurrent.TimeUnit;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.rules.RuleChain;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\nfinal class GlideBenchmarkRule implements TestRule {\n  private final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final BenchmarkRule benchmarkRule = new BenchmarkRule();\n\n  private final TestRule ruleChain = RuleChain.outerRule(benchmarkRule).around(tearDownGlide);\n\n  @NotNull\n  @Override\n  public Statement apply(@NotNull Statement base, @NotNull Description description) {\n    return ruleChain.apply(base, description);\n  }\n\n  void pauseTiming() {\n    benchmarkRule.getState().pauseTiming();\n  }\n\n  void resumeTiming() {\n    benchmarkRule.getState().resumeTiming();\n  }\n\n  BenchmarkRule getBenchmark() {\n    return benchmarkRule;\n  }\n\n  interface LoadStep<BeforeDataT> {\n    Object getModel(BeforeDataT beforeData) throws Exception;\n  }\n\n  interface BeforeStep<BeforeDataT> {\n    BeforeDataT act() throws Exception;\n  }\n\n  interface AfterStep<BeforeDataT> {\n    void act(BeforeDataT beforeData) throws Exception;\n  }\n\n  <T> void runBenchmark(BeforeStep<T> beforeStep, AfterStep<T> afterStep) throws Exception {\n    runBenchmark(\n        beforeStep,\n        new LoadStep<T>() {\n          @Override\n          public Object getModel(T beforeData) {\n            return beforeData;\n          }\n        },\n        afterStep);\n  }\n\n  <T> void runBenchmark(BeforeStep<T> beforeStep, LoadStep<T> loadStep, AfterStep<T> afterStep)\n      throws Exception {\n    BenchmarkState state = benchmarkRule.getState();\n    Context app = ApplicationProvider.getApplicationContext();\n    while (state.keepRunning()) {\n      state.pauseTiming();\n      Glide.get(app);\n      T beforeData = beforeStep.act();\n      state.resumeTiming();\n\n      Glide.with(app)\n          .load(loadStep.getModel(beforeData))\n          .diskCacheStrategy(DiskCacheStrategy.NONE)\n          .override(Target.SIZE_ORIGINAL)\n          .submit()\n          .get(15, TimeUnit.SECONDS);\n\n      state.pauseTiming();\n      tearDownGlide.tearDownGlide();\n      afterStep.act(beforeData);\n      state.resumeTiming();\n    }\n  }\n}\n"
  },
  {
    "path": "benchmark/src/androidTest/java/com/bumptech/glide/benchmark/data/DataOpener.java",
    "content": "package com.bumptech.glide.benchmark.data;\n\nimport android.os.ParcelFileDescriptor;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\n\n/** Converts test resources into various useful data types for benchmarking. */\npublic interface DataOpener<T> {\n  T acquire(@RawRes int resourceId) throws IOException;\n\n  void close(T data) throws IOException;\n\n  final class StreamOpener implements DataOpener<InputStream> {\n\n    @Override\n    public InputStream acquire(@RawRes int resourceId) {\n      return ApplicationProvider.getApplicationContext().getResources().openRawResource(resourceId);\n    }\n\n    @Override\n    public void close(InputStream data) throws IOException {\n      data.close();\n    }\n  }\n\n  final class ByteArrayBufferOpener implements DataOpener<ByteBuffer> {\n\n    @Override\n    public ByteBuffer acquire(@RawRes int resourceId) throws IOException {\n      InputStream is = null;\n      try {\n        is = new StreamOpener().acquire(resourceId);\n        return ByteBufferUtil.fromStream(is);\n      } finally {\n        if (is != null) {\n          try {\n            is.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n      }\n    }\n\n    @Override\n    public void close(ByteBuffer data) {}\n  }\n\n  final class InputStreamOverByteArrayBufferOpener implements DataOpener<InputStream> {\n\n    private final ByteArrayBufferOpener byteArrayBufferOpener = new ByteArrayBufferOpener();\n    @Nullable private ByteBuffer buffer;\n\n    @Override\n    public InputStream acquire(@RawRes int resourceId) throws IOException {\n      buffer = byteArrayBufferOpener.acquire(resourceId);\n      return ByteBufferUtil.toStream(buffer);\n    }\n\n    @Override\n    public void close(InputStream data) throws IOException {\n      data.close();\n      if (buffer != null) {\n        byteArrayBufferOpener.close(buffer);\n      }\n    }\n  }\n\n  final class FileOpener implements DataOpener<File> {\n\n    @Override\n    public File acquire(@RawRes int resourceId) throws IOException {\n      ByteBuffer byteBuffer = new ByteArrayBufferOpener().acquire(resourceId);\n      File tempFile =\n          File.createTempFile(\n              \"memory_mapped\", \"jpg\", ApplicationProvider.getApplicationContext().getCacheDir());\n      ByteBufferUtil.toFile(byteBuffer, tempFile);\n      return tempFile;\n    }\n\n    @Override\n    public void close(File data) {\n      if (!data.delete()) {\n        throw new IllegalStateException(\"Failed to delete: \" + data);\n      }\n    }\n  }\n\n  final class ByteArrayOpener implements DataOpener<byte[]> {\n\n    @Override\n    public byte[] acquire(@RawRes int resourceId) throws IOException {\n      InputStream is = null;\n      try {\n        is = new StreamOpener().acquire(resourceId);\n        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n        byte[] buffer = new byte[1024 * 1024];\n        int read;\n        while ((read = is.read(buffer, /* off= */ 0, buffer.length)) != -1) {\n          outputStream.write(buffer, /* off= */ 0, read);\n        }\n        return outputStream.toByteArray();\n      } finally {\n        if (is != null) {\n          try {\n            is.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n      }\n    }\n\n    @Override\n    public void close(byte[] data) {}\n  }\n\n  final class ParcelFileDescriptorOpener implements DataOpener<ParcelFileDescriptor> {\n\n    private final FileOpener fileOpener = new FileOpener();\n    @Nullable private File file;\n\n    @Override\n    public ParcelFileDescriptor acquire(@RawRes int resourceId) throws IOException {\n      file = fileOpener.acquire(resourceId);\n      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);\n    }\n\n    @Override\n    public void close(ParcelFileDescriptor data) throws IOException {\n      data.close();\n      if (file != null) {\n        fileOpener.close(file);\n      }\n    }\n  }\n\n  final class MemoryMappedByteBufferOpener implements DataOpener<ByteBuffer> {\n\n    private final FileOpener fileOpener = new FileOpener();\n    @Nullable private File file;\n\n    @Override\n    public ByteBuffer acquire(@RawRes int resourceId) throws IOException {\n      file = fileOpener.acquire(resourceId);\n      return ByteBufferUtil.fromFile(file);\n    }\n\n    @Override\n    public void close(ByteBuffer data) {\n      if (file != null) {\n        fileOpener.close(file);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "benchmark/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/BenchmarkDownsampler.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.app.Application;\nimport android.os.ParcelFileDescriptor;\nimport androidx.benchmark.BenchmarkState;\nimport androidx.benchmark.junit4.BenchmarkRule;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.benchmark.R;\nimport com.bumptech.glide.benchmark.data.DataOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.ByteArrayBufferOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.ByteArrayOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.FileOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.InputStreamOverByteArrayBufferOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.MemoryMappedByteBufferOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.ParcelFileDescriptorOpener;\nimport com.bumptech.glide.benchmark.data.DataOpener.StreamOpener;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;\nimport com.bumptech.glide.request.target.Target;\nimport com.google.common.collect.ImmutableList;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/** Benchmarks to compare the performance of the various data types supported by Downsampler. */\n@RunWith(AndroidJUnit4.class)\npublic class BenchmarkDownsampler {\n  private static final int SIZE = Target.SIZE_ORIGINAL;\n  private static final int RESOURCE_ID = R.raw.pixel3a_portrait;\n  private final Application app = ApplicationProvider.getApplicationContext();\n\n  @Rule public BenchmarkRule mBenchmarkRule = new BenchmarkRule();\n\n  @Test\n  public void testInputStream() throws IOException {\n    runBenchmark(new StreamOpener(), new InputStreamDecoder());\n  }\n\n  @Test\n  public void testByteBufferOverByteArray() throws IOException {\n    runBenchmark(new ByteArrayBufferOpener(), new ByteBufferDecoder());\n  }\n\n  @Test\n  public void testByteBufferOverFile() throws IOException {\n    runBenchmark(new MemoryMappedByteBufferOpener(), new ByteBufferDecoder());\n  }\n\n  @Test\n  public void testParcelFileDescriptorOverFile() throws IOException {\n    runBenchmark(new ParcelFileDescriptorOpener(), new ParcelFileDescriptorDecoder());\n  }\n\n  @Test\n  public void testFile() throws IOException {\n    runBenchmark(new FileOpener(), new FileDecoder());\n  }\n\n  @Test\n  public void testByteArray() throws IOException {\n    runBenchmark(new ByteArrayOpener(), new ByteArrayDecoder());\n  }\n\n  // A legacy case that's since been removed.\n  @Test\n  public void testInputStreamOverByteBufferOverByteArray() throws IOException {\n    runBenchmark(new InputStreamOverByteArrayBufferOpener(), new InputStreamDecoder());\n  }\n\n  private <T> void runBenchmark(DataOpener<T> opener, Decoder<T> decoder) throws IOException {\n    final BenchmarkState state = mBenchmarkRule.getState();\n    while (state.keepRunning()) {\n      state.pauseTiming();\n      T data = null;\n      try {\n        data = opener.acquire(RESOURCE_ID);\n        Downsampler downsampler = newDownsampler();\n        state.resumeTiming();\n\n        decoder.decode(downsampler, data, SIZE, SIZE);\n      } finally {\n        state.pauseTiming();\n        opener.close(data);\n        state.resumeTiming();\n      }\n    }\n  }\n\n  private interface Decoder<T> {\n    void decode(Downsampler downsampler, T data, int width, int height) throws IOException;\n  }\n\n  private static final class ByteBufferDecoder implements Decoder<ByteBuffer> {\n    @Override\n    public void decode(Downsampler downsampler, ByteBuffer data, int width, int height)\n        throws IOException {\n      downsampler.decode(data, width, height, new Options());\n    }\n  }\n\n  private static final class InputStreamDecoder implements Decoder<InputStream> {\n    @Override\n    public void decode(Downsampler downsampler, InputStream data, int width, int height)\n        throws IOException {\n      downsampler.decode(data, width, height, new Options());\n    }\n  }\n\n  private static final class ParcelFileDescriptorDecoder implements Decoder<ParcelFileDescriptor> {\n    @Override\n    public void decode(Downsampler downsampler, ParcelFileDescriptor data, int width, int height)\n        throws IOException {\n      downsampler.decode(data, width, height, new Options());\n    }\n  }\n\n  private static final class FileDecoder implements Decoder<File> {\n    @Override\n    public void decode(Downsampler downsampler, File data, int width, int height)\n        throws IOException {\n      downsampler.decode(data, width, height, new Options());\n    }\n  }\n\n  private static final class ByteArrayDecoder implements Decoder<byte[]> {\n    @Override\n    public void decode(Downsampler downsampler, byte[] data, int width, int height)\n        throws IOException {\n      downsampler.decode(data, width, height, new Options());\n    }\n  }\n\n  private Downsampler newDownsampler() {\n    ImmutableList<ImageHeaderParser> imageHeaderParsers =\n        ImmutableList.of(new DefaultImageHeaderParser(), new ExifInterfaceImageHeaderParser());\n    return new Downsampler(\n        imageHeaderParsers,\n        app.getResources().getDisplayMetrics(),\n        new LruBitmapPool(20 * 1024 * 1024),\n        new LruArrayPool(5 * 1024 * 1024));\n  }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "import org.gradle.api.tasks.testing.logging.TestExceptionFormat\nimport se.bjurr.violations.gradle.plugin.ViolationsTask\n\nbuildscript {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n\n    dependencies {\n        classpath libs.android.gradle\n        if (!hasProperty('DISABLE_ERROR_PRONE')) {\n            classpath libs.errorprone.gradle\n        }\n        classpath libs.proguard.gradle\n        classpath libs.violations\n        classpath libs.androidx.benchmark.gradle\n        classpath libs.kotlin.gradle\n        classpath libs.ksp.gradle.plugin\n        classpath libs.coroutines.binarycompat.gradle\n        classpath libs.dokka.gradle\n        classpath libs.vanniktech\n        classpath 'com.guardsquare:proguard-gradle:' + (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11) ? '7.3.2' : '7.1.0')\n    }\n}\n\nplugins {\n    alias libs.plugins.ktfmt\n}\n\napply plugin: 'binary-compatibility-validator'\napply plugin: 'org.jetbrains.dokka'\n\napiValidation {\n    ignoredProjects += [\"ksp\", \"test\", \"gallery\", \"integrationtest\", \"sqljournaldiskcache\"]\n    nonPublicMarkers += [\"com.bumptech.glide.integration.ktx.InternalGlideApi\"]\n}\n\n// See http://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html.\nif (JavaVersion.current().isJava8Compatible()) {\n    allprojects {\n        tasks.withType(Javadoc) {\n            options.addStringOption('Xdoclint:none', '-quiet')\n        }\n    }\n}\n\ndokkaHtmlMultiModule.configure {\n    moduleName.set(\"Glide\")\n}\nafterEvaluate {\n    tasks.named(\"dokkaHtmlMultiModule\") {\n        pluginsMapConfiguration.set(\n                [\n        \"org.jetbrains.dokka.base.DokkaBase\": \"\"\"{\n          \"customStyleSheets\": [\"${projectDir.toString()}/static/logo-styles.css\"],\n          \"customAssets\" : [\"${projectDir.toString()}/static/logo-icon.svg\", \"${projectDir.toString()}/static/glide_circle_logo.png\"]\n         }\"\"\"\n                ]\n        )\n    }\n}\n\nsubprojects { project ->\n\n    // Exclude packages not intended for public use.\n    if ([\n          \"testutil\",\n          \"flickr\",\n          \"giphy\",\n          \"imgur\",\n          \"svg\",\n          \"gallery\",\n          \"contacturi\",\n          \"test\",\n          \"gif_decoder\",\n          \"gifencoder\",\n          \"compiler\",\n          \"benchmark\",\n          \"integrationtest\",\n          \"instrumentation\",\n          \"glide-parent\",\n          \"integration\",\n          \"samples\",\n          \"third_party\"\n    ].contains(project.getName())) {\n        afterEvaluate {\n            project.apply plugin: 'org.jetbrains.dokka'\n\n            project.tasks.dokkaHtmlPartial.enabled = false\n        }\n    } else {\n        apply plugin: \"com.ncorti.ktfmt.gradle\"\n        ktfmt {\n            kotlinLangStyle()\n        }\n\n        afterEvaluate {\n            project.apply plugin: 'org.jetbrains.dokka'\n\n            project.tasks.dokkaHtmlPartial.configure {\n                dokkaSourceSets {\n                    // Kotlin works out of the box\n                     if (!project.plugins.hasPlugin(\"kotlin-android\") && project.plugins.hasPlugin(\"com.android.library\")) {\n                        // Java Android modules\n                        register(\"main\") {\n                            sourceRoots.from(project.android.sourceSets.main.java.srcDirs)\n\n                        }\n                    } else if (project.plugins.hasPlugin(\"java\") && \"ksp\" != project.getName()) {\n                        // Java only modules (ksp is not useful and uses multiple plugins)\n                        register(\"main\") {\n                            sourceRoots.from(sourceSets.main.java.srcDirs)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    afterEvaluate {\n        if (project.plugins.hasPlugin(\"com.android.application\")) {\n            project.dependencies {\n                // Hack around some version mismatches: https://stackoverflow.com/questions/75263047/duplicate-class-in-kotlin-android\n                implementation(platform(libs.kotlin.bom))\n            }\n        }\n    }\n\n    tasks.withType(JavaCompile) {\n        // gifencoder is a legacy project that has a ton of warnings and is basically never\n        // modified, so we're not going to worry about cleaning it up.\n        // Imgur uses generated code from dagger that has warnings.\n        if (\"gifencoder\" != project.getName() && \"imgur\" != project.getName()) {\n          options.compilerArgs.addAll([\n            //Treat all warnings as errors.\n            \"-Werror\",\n            //Enable all warnings.\n            \"-Xlint:all\",\n            // Disable warnings about source 7 being obsolete.\n            \"-Xlint:-options\",\n            // Java expects every annotation to have a processor, but we use\n            // javax.annotation.Nullable, which doesn't have one.\n            \"-Xlint:-processing\",\n            // See https://github.com/google/dagger/issues/945\n            // and https://bugs.openjdk.java.net/browse/JDK-8190452\n            \"-Xlint:-classfile\",\n             // Disable deprecation warnings for ViewTarget/BaseTarget for now.\n            \"-Xlint:-deprecation\",\n          ])\n        }\n    }\n\n    tasks.withType(Test) {\n        testLogging {\n            exceptionFormat = TestExceptionFormat.FULL\n        }\n    }\n\n    // Avoid issues like #2452.\n    tasks.withType(Jar) {\n        duplicatesStrategy = DuplicatesStrategy.FAIL\n    }\n\n    apply plugin: 'checkstyle'\n\n    checkstyle {\n        toolVersion = '8.45.1'\n    }\n\n    checkstyle {\n        configFile = rootProject.file('checkstyle.xml')\n        configProperties.checkStyleConfigDir = rootProject.rootDir\n    }\n\n    task checkstyle(type: Checkstyle) {\n        source 'src'\n        include '**/*.java'\n        exclude '**/gen/**'\n        // Caught by the violations plugin.\n        ignoreFailures = true\n\n        // empty classpath\n        classpath = files()\n    }\n\n    apply plugin: \"se.bjurr.violations.violations-gradle-plugin\"\n\n    task violations(type: ViolationsTask) {\n        minSeverity = 'INFO'\n        detailLevel = 'VERBOSE'\n        maxViolations = 6\n        diffMaxViolations = 0\n\n        // Formats are listed here: https://github.com/tomasbjerre/violations-lib\n        def dir = projectDir.absolutePath\n        violations = [\n                [\"PMD\",         dir, \".*/pmd/.*\\\\.xml\\$\",        \"PMD\"],\n                [\"ANDROIDLINT\", dir, \".*/lint-results\\\\.xml\\$\",  \"AndroidLint\"],\n                [\"CHECKSTYLE\",  dir, \".*/checkstyle/.*\\\\.xml\\$\", \"Checkstyle\"],\n        ]\n    }\n\n    afterEvaluate {\n        if (project.hasProperty(\"android\")\n                && project.name != 'pmd' ) {\n            android {\n                lint {\n                    warningsAsErrors true\n                    quiet true\n                    // Caught by the violations plugin.\n                    abortOnError false\n                }\n\n                // We don't need a BuildConfig constants class.\n                buildFeatures {\n                    buildConfig = false\n                }\n\n                // Signing isn't relevant to a library. Our signing is done via\n                // a GPG key at upload time, there's no APK to sign.\n                buildTypes {\n                    release {\n                        signingConfig signingConfigs.debug\n                    }\n                }\n            }\n        }\n\n        if (project.tasks.findByName('check')) {\n            check.dependsOn('checkstyle')\n            check.finalizedBy violations\n        }\n    }\n}\n"
  },
  {
    "path": "checkstyle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE module PUBLIC \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\" \"http://www.puppycrawl.com/dtds/configuration_1_3.dtd\">\n<module name=\"Checker\">\n    <module name=\"FileLength\"/>\n    <module name=\"FileTabCharacter\"/>\n\n    <module name=\"SuppressionFilter\">\n        <property name=\"file\" value=\"${checkStyleConfigDir}/checkstyle_suppressions.xml\" />\n    </module>\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    <!-- Ensure trailling newline for compatibility -->\n    <module name=\"NewlineAtEndOfFile\">\n        <property name=\"lineSeparator\" value=\"lf\"/>\n    </module>\n\n    <!-- Space after 'for' and 'if' -->\n    <module name=\"RegexpSingleline\">\n        <property name=\"format\" value=\"^\\s*(for|if)\\b[^ ]\"/>\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    <module name=\"SuppressWarningsFilter\" />\n    <module name=\"TreeWalker\">\n        <module name=\"SuppressWarningsHolder\" />\n\n        <!-- Checks for uncommented main() methods (debugging leftovers). -->\n        <!-- Checks that long constants are defined with an upper ell. -->\n        <!-- See http://checkstyle.sourceforge.net/config_misc.html#UpperEll -->\n        <module name=\"UpperEll\" />\n\n        <!-- Checks the style of array type definitions. -->\n        <!-- See http://checkstyle.sourceforge.net/config_misc.html#ArrayTypeStyle -->\n        <module name=\"ArrayTypeStyle\" />\n\n        <!-- Checks that the outer type name and the file name match. -->\n        <!-- See http://checkstyle.sourceforge.net/config_misc.html#OuterTypeFilename -->\n        <module name=\"OuterTypeFilename\" />\n\n        <!-- Validates Javadoc comments to help ensure they are well formed. -->\n        <!-- See http://checkstyle.sourceforge.net/config_javadoc.html#JavadocStyle -->\n        <module name=\"JavadocStyle\" />\n        <module name=\"JavadocType\">\n            <property name=\"scope\" value=\"public\"/>\n        </module>\n\n        <!-- Each of these naming modules validates identifiers for particular\n                code elements. -->\n        <!-- See http://checkstyle.sourceforge.net/config_naming.html -->\n        <module name=\"ConstantName\">\n            <property name=\"format\" value=\"^[A-Z][A-Z0-9\\$]*(_[A-Z0-9\\$]+)*$\" />\n        </module>\n        <module name=\"LocalFinalVariableName\" />\n        <module name=\"LocalVariableName\" />\n        <module name=\"MemberName\">\n            <property name=\"format\" value=\"^[a-z][a-zA-Z0-9_\\$]*$\" />\n        </module>\n        <module name=\"MethodName\" >\n            <property name=\"format\" value=\"^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$\"/>\n        </module>\n        <module name=\"PackageName\" />\n        <module name=\"ParameterName\" />\n        <module name=\"StaticVariableName\" />\n        <module name=\"TypeName\" />\n\n        <!-- Checks for imports. -->\n        <!-- See http://checkstyle.sourceforge.net/config_imports.html -->\n        <module name=\"AvoidStarImport\"/>\n        <module name=\"RedundantImport\"/>\n        <module name=\"UnusedImports\"/>\n        <!-- Default sun.* packages -->\n        <module name=\"IllegalImport\">\n            <property name=\"illegalPkgs\" value=\"sun\" />\n            <message key=\"import.illegal\" value=\"Import from illegal package - {0}. Programs that contain direct calls to the sun.* packages are not 100% Pure Java.\" />\n        </module>\n        <!-- Prevent importing JUnit 3 classes and Assert methods -->\n        <module name=\"IllegalImport\">\n            <property name=\"illegalPkgs\" value=\"junit\" />\n            <message key=\"import.illegal\" value=\"Import from illegal package - {0}. Tests are written in JUnit 4, use org.junit.* equivalents.\" />\n        </module>\n        <!-- Prevent importing Mockito matchers directly -->\n        <module name=\"IllegalImport\">\n            <property name=\"illegalPkgs\" value=\"org.mockito.internal\" />\n            <message key=\"import.illegal\" value=\"Import from illegal package - {0}. Use org.mockito.Matchers to instantiate argument matchers.\" />\n        </module>\n\n        <!-- Modifier Checks. -->\n        <!-- See http://checkstyle.sourceforge.net/config_modifier.html -->\n        <module name=\"ModifierOrder\" />\n\n        <!-- Checks for blocks. -->\n        <!-- See http://checkstyle.sourceforge.net/config_blocks.html -->\n        <module name=\"AvoidNestedBlocks\">\n            <property name=\"allowInSwitchCase\" value=\"true\" />\n        </module>\n        <module name=\"EmptyBlock\" >\n            <property name=\"option\" value=\"text\"/>\n        </module>\n        <module name=\"NeedBraces\"/>\n\n        <module name=\"LeftCurly\" />\n        <module name=\"RightCurly\">\n            <property name=\"tokens\"\n              value=\"LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_ELSE\" />\n        </module>\n\n        <!-- Checks for common coding problems. -->\n        <!-- See http://checkstyle.sourceforge.net/config_coding.html -->\n        <module name=\"CovariantEquals\" />\n        <module name=\"DefaultComesLast\" />\n        <module name=\"EmptyStatement\" />\n        <module name=\"EqualsHashCode\" />\n        <module name=\"NoClone\" />\n        <module name=\"NoFinalizer\" />\n        <module name=\"OneStatementPerLine\" />\n        <module name=\"IllegalInstantiation\"/>\n        <module name=\"SimplifyBooleanExpression\" />\n        <module name=\"SimplifyBooleanReturn\" />\n        <module name=\"StringLiteralEquality\" />\n        <module name=\"UnnecessaryParentheses\" />\n\n        <!-- Checks for class design. -->\n        <!-- See http://checkstyle.sourceforge.net/config_design.html -->\n        <module name=\"FinalClass\" />\n        <module name=\"InterfaceIsType\" />\n    </module>\n</module>"
  },
  {
    "path": "checkstyle_suppressions.xml",
    "content": "<?xml version=\"1.0\"?>\n\n<!DOCTYPE suppressions PUBLIC\n    \"-//Puppy Crawl//DTD Suppressions 1.1//EN\"\n    \"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd\">\n\n<suppressions>\n    <suppress files=\".*[/\\\\]library[/\\\\]src[/\\\\]main[/\\\\]java[/\\\\]com[/\\\\]bumptech[/\\\\]glide[/\\\\]GlideExperiments.java\" checks=\"FinalClass\"/>\n    <suppress files=\".*[/\\\\]library[/\\\\]src[/\\\\]main[/\\\\]java[/\\\\]com[/\\\\]bumptech[/\\\\]glide[/\\\\]util[/\\\\]CachedHashCodeArrayMap.java\" checks=\"EqualsHashCodeCheck\"/>\n    <suppress files=\".*[/\\\\]library[/\\\\]test[/\\\\]src[/\\\\]test[/\\\\].*\" checks=\"Javadoc.*\"/>\n    <suppress files=\".*[/\\\\]annotation[/\\\\]compiler[/\\\\]test[/\\\\]src[/\\\\]test[/\\\\]resources[/\\\\].*\" checks=\".*\"/>\n    <suppress files=\".*[/\\\\]integration[/\\\\]concurrent[/\\\\]src[/\\\\]test[/\\\\].*\" checks=\".*\"/>\n    <suppress files=\".*[/\\\\]integration[/\\\\]sqljournaldiskcache[/\\\\]src[/\\\\]test[/\\\\].*\" checks=\"Javadoc.*\"/>\n    <suppress files=\".*[/\\\\]integration[/\\\\]sqljournaldiskcache[/\\\\]src[/\\\\]main[/\\\\].*\" checks=\"InterfaceIsTypeCheck.*\"/>\n    <suppress files=\".*[/\\\\]testutil[/\\\\]src[/\\\\].*\" checks=\"Javadoc.*\"/>\n    <suppress files=\".*[/\\\\]benchmark[/\\\\]src[/\\\\].*\" checks=\"Javadoc.*\"/>\n    <suppress files=\".*[/\\\\]instrumentation[/\\\\]src[/\\\\].*\" checks=\"Javadoc.*\"/>\n    <suppress files=\".*[/\\\\]instrumentation[/\\\\]src[/\\\\]androidTest[/\\\\].*\" checks=\"Javadoc.*\"/>\n    <suppress files=\".*[/\\\\]instrumentation[/\\\\]src[/\\\\]androidTest[/\\\\].*[/\\\\]ResourceIds\" checks=\".*\"/>\n    <suppress files=\".*[/\\\\]gif_encoder[/\\\\].*\" checks=\".*\"/>\n    <suppress files=\".*RequestBuilder.java|ChildLoadProvider.java|TransitionOptions.java|BaseDecodeOptions.java|RequestOptions.java\" checks=\"NoClone\" />\n</suppressions>\n\n"
  },
  {
    "path": "glide/build.gradle",
    "content": "import com.android.build.gradle.api.LibraryVariant\n\n/**\n * This module is used for two things:\n *     <ul>\n *         <li>Compiling a single unified set of javadocs for Glide\n *         <li>Providing a jar version of Glide for internal libraries, like\n *         Glide's annotation processor.\n *     </ul>\n *\n * <p>Previously this module was used to produce a release jar for Glide, but\n * we've long since stopped releasing the jar. Now all release artifacts come\n * from the upload script, which uploads aars for each production submodule\n */\n\napply plugin: 'java'\n\n// The paths of Android projects that should be included only in Javadoc, not in the jar.\nstatic def getAndroidPathsForJavadoc() {\n    [\n            ':integration:concurrent',\n            ':integration:gifencoder',\n            ':integration:okhttp',\n            ':integration:okhttp3',\n            ':integration:recyclerview',\n            ':integration:volley',\n            ':library',\n            ':mocks',\n            ':third_party:disklrucache',\n            ':third_party:gif_decoder',\n    ]\n}\n\nstatic def getAndroidPathsForJar() {\n    [':library', ':third_party:disklrucache', ':third_party:gif_decoder']\n}\n\n// The paths of Java projects that should be included only in Javadoc, not in the jar.\nstatic def getJavaPathsForJavadoc() {\n    [':annotation']\n}\n\n(getAndroidPathsForJavadoc() + getJavaPathsForJavadoc()).each {\n    evaluationDependsOn(it)\n}\n\ndef asProjects(paths) {\n    paths.collect { String path -> project(path) }\n}\n\ndef getAndroidSdkDirectory() {\n    project(':library').android.sdkDirectory\n}\n\ndef getAndroidCompileSdkVersion() {\n    project(':library').android.compileSdkVersion\n}\n\ndef getAndroidProjectsForJavadoc() {\n    asProjects(getAndroidPathsForJavadoc())\n}\n\ndef getAndroidLibraryVariantsForJar() {\n    getAndroidLibraryVariantsForProjects(asProjects(getAndroidPathsForJar()))\n}\n\ndef getAndroidLibraryVariantsForJavadoc() {\n    getAndroidLibraryVariantsForProjects(getAndroidProjectsForJavadoc())\n}\n\ndef getAndroidLibraryVariantsForProjects(projects) {\n    projects.collect { project ->\n        project.android.libraryVariants.findAll { type ->\n            type.buildType.name.equalsIgnoreCase(\"release\")\n        }\n    }.sum()\n}\n\ndef getSourceFilesForJavadoc() {\n    getAndroidProjectsForJavadoc().collect { project ->\n      project.android.sourceSets.main.java.srcDirs\n    }\n}\n\ndef getAndroidJar() {\n    \"${getAndroidSdkDirectory()}/platforms/${getAndroidCompileSdkVersion()}/android.jar\"\n}\n\nproject.archivesBaseName = \"${POM_ARTIFACT_ID}-${VERSION_NAME}\"\n\n// Generate javadocs and sources containing batched documentation and sources for all internal\n// projects.\ndef javadocTask = tasks.create(\"releaseJavadoc\", Javadoc) {\n    source = getSourceFilesForJavadoc()\n\n    doFirst {\n        it.classpath =\n                project.files(\n                        getAndroidJar(),\n                        getAndroidLibraryVariantsForJavadoc().collect {\n                            LibraryVariant lib ->\n                                lib.getJavaCompileProvider().get().classpath.files\n                        },\n                        // Finds dependencies of Android packages that would otherwise be\n                        // ignored (Volley in particular)\n                        getAndroidProjectsForJavadoc().collect { Project project ->\n                            project.file('build/intermediates/javac/release/classes')\n                        }\n        )\n    }\n\n    options {\n        links(\"http://docs.oracle.com/javase/7/docs/api/\")\n        links(\"https://square.github.io/okhttp/3.x/okhttp/\")\n        links(\"https://square.github.io/okhttp/2.x/okhttp/\")\n        links(\"http://d.android.com/reference\")\n    }\n\n    exclude '**/R.java'\n}\n\ndef cleanJavadocTask = task(\"cleanReleaseJavadoc\", type: Delete) {\n    delete javadocTask.destinationDir\n} as Task\nclean.dependsOn(cleanJavadocTask)\n\ndef javadocJarTask = task(\"releaseJavadocJar\", type: Jar) {\n    from javadocTask.destinationDir\n} as Task\n\njavadocJarTask.dependsOn(javadocTask)\n\n(getAndroidProjectsForJavadoc()).each {\n    project ->\n        releaseJavadoc.dependsOn(project.tasks.compileReleaseSources)\n        jar.dependsOn(project.tasks.compileReleaseSources)\n}\n\n\njar {\n    from files(\n            getAndroidLibraryVariantsForJar().collect { LibraryVariant variant ->\n                variant.getJavaCompileProvider().get().destinationDirectory\n            }\n    )\n    exclude \"**/R.class\"\n    exclude \"**/R\\$*.class\"\n    exclude \"android/**\"\n    exclude \"**/BuildConfig.class\"\n}\n \nartifacts {\n    archives releaseJavadocJar {\n        archiveClassifier = 'javadoc'\n    }\n}\n\n"
  },
  {
    "path": "glide/gradle.properties",
    "content": "POM_NAME=Glide Full\nPOM_ARTIFACT_ID=glide-full\nPOM_PACKAGING=jar\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\navif = \"1.1.1.14d8e3c4\"\ngson = \"2.8.2\"\npmd = \"6.0.0\"\ndagger = \"2.47\"\ncompose = \"1.5.1\"\nkotlin = \"1.9.20\"\nmockito = \"5.3.1\"\nretrofit = \"2.3.0\"\ncoroutines = \"1.8.0\"\nksp = \"1.9.20-1.0.14\"\nerrorprone = \"2.18.0\"\nmin-sdk-version = \"14\"\ntarget-sdk-version = \"32\"\nandroidx-espresso = \"3.5.1\"\nandroidx-fragment = \"1.6.1\"\nokhttp-min-sdk-version = \"21\"\nkotlin-compiler-extension = \"1.5.5\"\nandroidx-benchmark = \"1.2.0-beta05\"\ncompile-sdk-version = \"android-36\"\nandroidx-multidex = \"2.0.1\"\nautoservice = \"1.0-rc3\"\nautoservice-annotations = \"1.0.1\"\nandroid-gradle = \"8.1.1\"\nandroidx-cardview = \"1.0.0\"\nandroidx-core = \"1.12.0\"\nandroidx-annotation = \"1.7.1\"\nandroidx-appcompat = \"1.6.1\"\nandroidx-exifinterface = \"1.3.6\"\nandroidx-futures = \"1.1.0\"\nandroidx-junit = \"1.1.5\"\nandroidx-lifecycle-runtime = \"2.8.2\"\nandroidx-recyclerview = \"1.3.1\"\nandroidx-test-core = \"1.4.0\"\nandroidx-test-ktx = \"1.5.0\"\nandroidx-test-rules = \"1.4.0\"\nandroidx-test-runner = \"1.4.0\"\nandroidx-tracing = \"1.0.0\"\nandroidx-vectordrawable = \"1.1.0\"\nproguard-gradle = \"7.1.0\"\ncoroutines-binarycompat-gradle = \"0.18.1\"\ncronet = \"17.0.1\"\ndokka-gradle = \"1.8.20\"\ndrawablepainter = \"0.25.1\"\nerrorprone-gradle = \"2.0.2\"\nfindbugs-jsr305 = \"3.0.2\"\nguava = \"28.1-android\"\nguava-testlib = \"18.0\"\njavapoet = \"1.9.0\"\njunit = \"4.13.2\"\nkotlinpoet = \"1.12.0\"\nksp-autoservice = \"1.0.0\"\nksp-compiletesting = \"1.6.0\"\nmockwebserver = \"3.0.0-RC1\"\nokhttp2 = \"2.7.5\"\nokhttp3 = \"3.10.0\"\nokhttp4 = \"4.10.0\"\nrobolectric = \"4.11.1\"\nrx-android = \"1.2.1\"\nrx-java = \"1.3.8\"\nsvg = \"1.2.1\"\ntruth = \"1.4.4\"\nviolations = \"1.8\"\nvolley = \"1.2.1\"\nvanniktech = \"0.34.0\"\nktfmt = \"0.25.0\"\n\n[libraries]\nandroidx-multidex = { group = \"androidx.multidex\", name = \"multidex\", version.ref = \"androidx-multidex\" }\nautoservice = { group = \"com.google.auto.service\", name = \"auto-service\", version.ref = \"autoservice\" }\nautoservice-annotations = { group = \"com.google.auto.service\", name = \"auto-service-annotations\", version.ref = \"autoservice-annotations\" }\nandroid-gradle = { group = \"com.android.tools.build\", name = \"gradle\", version.ref = \"android-gradle\" }\nandroidx-cardview = { group = \"androidx.cardview\", name = \"cardview\", version.ref = \"androidx-cardview\" }\nandroidx-core = { group = \"androidx.core\", name = \"core\", version.ref = \"androidx-core\" }\nandroidx-annotation = { group = \"androidx.annotation\", name = \"annotation\", version.ref = \"androidx-annotation\" }\nandroidx-appcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version.ref = \"androidx-appcompat\" }\nandroidx-core-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"androidx-core\" }\nandroidx-exifinterface = { group = \"androidx.exifinterface\", name = \"exifinterface\", version.ref = \"androidx-exifinterface\" }\nandroidx-futures = { group = \"androidx.concurrent\", name = \"concurrent-futures\", version.ref = \"androidx-futures\" }\nandroidx-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"androidx-junit\" }\nandroidx-lifecycle-runtime-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-compose\", version.ref = \"androidx-lifecycle-runtime\" }\nandroidx-lifecycle-runtime-testing = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-testing\", version.ref = \"androidx-lifecycle-runtime\" }\nandroidx-recyclerview = { group = \"androidx.recyclerview\", name = \"recyclerview\", version.ref = \"androidx-recyclerview\" }\nandroidx-test-core = { group = \"androidx.test\", name = \"core\", version.ref = \"androidx-test-core\" }\nandroidx-test-ktx = { group = \"androidx.test\", name = \"core-ktx\", version.ref = \"androidx-test-ktx\" }\nandroidx-test-ktx-junit = { group = \"androidx.test.ext\", name = \"junit-ktx\", version.ref = \"androidx-junit\" }\nandroidx-test-rules = { group = \"androidx.test\", name = \"rules\", version.ref = \"androidx-test-rules\" }\nandroidx-test-runner = { group = \"androidx.test\", name = \"runner\", version.ref = \"androidx-test-runner\" }\nandroidx-tracing = { group = \"androidx.tracing\", name = \"tracing\", version.ref = \"androidx-tracing\" }\nandroidx-vectordrawable = { group = \"androidx.vectordrawable\", name = \"vectordrawable-animated\", version.ref = \"androidx-vectordrawable\" }\navif = { module = \"org.aomedia.avif.android:avif\", version.ref = \"avif\" }\ngson = { module = \"com.google.code.gson:gson\", version.ref = \"gson\" }\nproguard-gradle = { group = \"com.guardsquare\", name = \"proguard-gradle\", version.ref = \"proguard-gradle\" }\ncompose-material = { group = \"androidx.compose.material\", name = \"material\", version.ref = \"compose\" }\ncoroutines-binarycompat-gradle = { group = \"org.jetbrains.kotlinx\", name = \"binary-compatibility-validator\", version.ref = \"coroutines-binarycompat-gradle\" }\ncronet = { group = \"com.google.android.gms\", name = \"play-services-cronet\", version.ref = \"cronet\" }\ndokka-gradle = { group = \"org.jetbrains.dokka\", name = \"dokka-gradle-plugin\", version.ref = \"dokka-gradle\" }\ndrawablepainter = { group = \"com.google.accompanist\", name = \"accompanist-drawablepainter\", version.ref = \"drawablepainter\" }\nerrorprone-gradle = { group = \"net.ltgt.gradle\", name = \"gradle-errorprone-plugin\", version.ref = \"errorprone-gradle\" }\nfindbugs-jsr305 = { group = \"com.google.code.findbugs\", name = \"jsr305\", version.ref = \"findbugs-jsr305\" }\nguava = { group = \"com.google.guava\", name = \"guava\", version.ref = \"guava\" }\nguava-testlib = { group = \"com.google.guava\", name = \"guava-testlib\", version.ref = \"guava-testlib\" }\njavapoet = { group = \"com.squareup\", name = \"javapoet\", version.ref = \"javapoet\" }\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" }\nkotlinpoet = { group = \"com.squareup\", name = \"kotlinpoet\", version.ref = \"kotlinpoet\" }\nksp-autoservice = { group = \"dev.zacsweers.autoservice\", name = \"auto-service-ksp\", version.ref = \"ksp-autoservice\" }\nksp-compiletesting = { group = \"com.github.tschuchortdev\", name = \"kotlin-compile-testing-ksp\", version.ref = \"ksp-compiletesting\" }\nmockwebserver = { group = \"com.squareup.okhttp3\", name = \"mockwebserver\", version.ref = \"mockwebserver\" }\nokhttp2 = { group = \"com.squareup.okhttp\", name = \"okhttp\", version.ref = \"okhttp2\" }\nokhttp3 = { group = \"com.squareup.okhttp3\", name = \"okhttp\", version.ref = \"okhttp3\" }\nokhttp4 = { group = \"com.squareup.okhttp3\", name = \"okhttp\", version.ref = \"okhttp4\" }\nrobolectric = { group = \"org.robolectric\", name = \"robolectric\", version.ref = \"robolectric\" }\nrx-android = { group = \"io.reactivex\", name = \"rxandroid\", version.ref = \"rx-android\" }\nrx-java = { group = \"io.reactivex\", name = \"rxjava\", version.ref = \"rx-java\" }\nsvg = { group = \"com.caverock\", name = \"androidsvg\", version.ref = \"svg\" }\ntruth = { group = \"com.google.truth\", name = \"truth\", version.ref = \"truth\" }\nviolations = { group = \"se.bjurr.violations\", name = \"violations-gradle-plugin\", version.ref = \"violations\" }\nvolley = { group = \"com.android.volley\", name = \"volley\", version.ref = \"volley\" }\nvanniktech = { group = \"com.vanniktech\", name = \"gradle-maven-publish-plugin\", version.ref = \"vanniktech\" }\nandroidx-benchmark-gradle = { group = \"androidx.benchmark\", name = \"benchmark-gradle-plugin\", version.ref = \"androidx-benchmark\" }\nandroidx-benchmark-junit = { group = \"androidx.benchmark\", name = \"benchmark-junit4\", version.ref = \"androidx-benchmark\" }\nandroidx-espresso = { group = \"androidx.test.espresso\", name = \"espresso-core\", version.ref = \"androidx-espresso\" }\nandroidx-espresso-idling = { group = \"androidx.test.espresso.idling\", name = \"idling-concurrent\", version.ref = \"androidx-espresso\" }\nandroidx-fragment = { group = \"androidx.fragment\", name = \"fragment\", version.ref = \"androidx-fragment\" }\nandroidx-fragment-ktx = { group = \"androidx.fragment\", name = \"fragment-ktx\", version.ref = \"androidx-fragment\" }\ncompose-foundation = { group = \"androidx.compose.foundation\", name = \"foundation\", version.ref = \"compose\" }\ncompose-ui = { group = \"androidx.compose.ui\", name = \"ui\", version.ref = \"compose\" }\ncompose-ui-testmanifest = { group = \"androidx.compose.ui\", name = \"ui-test-manifest\", version.ref = \"compose\" }\ncompose-ui-testjunit4 = { group = \"androidx.compose.ui\", name = \"ui-test-junit4\", version.ref = \"compose\" }\ncoroutines-android = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-coroutines-android\", version.ref = \"coroutines\" }\ncoroutines-core = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-coroutines-core\", version.ref = \"coroutines\" }\ncoroutines-test = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-coroutines-test\", version.ref = \"coroutines\" }\ndagger-runtime = { group = \"com.google.dagger\", name = \"dagger\", version.ref = \"dagger\" }\ndagger-compiler = { group = \"com.google.dagger\", name = \"dagger-compiler\", version.ref = \"dagger\" }\ndagger-android = { group = \"com.google.dagger\", name = \"dagger-android\", version.ref = \"dagger\" }\ndagger-android-processor = { group = \"com.google.dagger\", name = \"dagger-android-processor\", version.ref = \"dagger\" }\nerrorprone-annotations = { group = \"com.google.errorprone\", name = \"error_prone_annotations\", version.ref = \"errorprone\" }\nerrorprone-core = { group = \"com.google.errorprone\", name = \"error_prone_core\", version.ref = \"errorprone\" }\nkotlin-junit = { group = \"org.jetbrains.kotlin\", name = \"kotlin-test-junit\", version.ref = \"kotlin\" }\nkotlin-jdk7 = { group = \"org.jetbrains.kotlin\", name = \"kotlin-stdlib-jdk7\", version.ref = \"kotlin\" }\nkotlin-gradle = { group = \"org.jetbrains.kotlin\", name = \"kotlin-gradle-plugin\", version.ref = \"kotlin\" }\nkotlin-test = { group = \"org.jetbrains.kotlin\", name = \"kotlin-test\", version.ref = \"kotlin\" }\nkotlin-bom = { group = \"org.jetbrains.kotlin\", name = \"kotlin-bom\", version.ref = \"kotlin\" }\nksp-api = { group = \"com.google.devtools.ksp\", name = \"symbol-processing-api\", version.ref = \"ksp\" }\nksp-gradle-plugin = { group = \"com.google.devtools.ksp\", name = \"com.google.devtools.ksp.gradle.plugin\", version.ref = \"ksp\" }\nmockito-core = { group = \"org.mockito\", name = \"mockito-core\", version.ref = \"mockito\" }\nmockito-android = { group = \"org.mockito\", name = \"mockito-android\", version.ref = \"mockito\" }\nretrofit-runtime = { group = \"com.squareup.retrofit2\", name = \"retrofit\", version.ref = \"retrofit\" }\nretrofit-gson = { group = \"com.squareup.retrofit2\", name = \"converter-gson\", version.ref = \"retrofit\" }\nretrofit-rxjava = { group = \"com.squareup.retrofit2\", name = \"adapter-rxjava\", version.ref = \"retrofit\" }\n\n[plugins]\nktfmt = { id = \"com.ncorti.ktfmt.gradle\", version.ref = \"ktfmt\" }"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.4-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "## For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n#\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx1024m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n#\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n#Sun Jun 05 16:53:18 EST 2022\n\n## Grouping\nGROUP=com.github.bumptech.glide\n\n## Metadata\nPOM_DESCRIPTION=A fast and efficient image loading library for Android focused on smooth scrolling.\nPOM_DEVELOPER_EMAIL=judds@google.com\nPOM_DEVELOPER_ID=sjudd\nPOM_DEVELOPER_NAME=Sam Judd\nPOM_SCM_CONNECTION=scm\\:git@github.com\\:bumptech/glide.git\nPOM_SCM_DEV_CONNECTION=scm\\:git@github.com\\:bumptech/glide.git\nPOM_SCM_URL=https\\://github.com/bumptech/glide\nPOM_URL=https\\://github.com/bumptech/glide\n\n## Gradle config\nandroid.useAndroidX=true\norg.gradle.configureondemand=false\norg.gradle.daemon=true\norg.gradle.jvmargs=-Xmx4096M\nTEST_JVM_MEMORY_SIZE=4096M\n\n## Glide versioning - these may be overwritten in lower level gradle.properties files\nVERSION_MAJOR=5\nVERSION_MINOR=0\nVERSION_PATCH=5\nVERSION_NAME=5.0.5\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\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#      https://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# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\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    if ! command -v java >/dev/null 2>&1\n    then\n        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.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\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='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@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\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\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=\"-Xmx64m\" \"-Xms64m\"\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% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\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 execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=\r\n\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%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 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\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "instrumentation/build.gradle.kts",
    "content": "tasks.configureEach {\n    if (name == \"lint\") {\n        enabled = false\n    }\n}\n\nplugins {\n    id(\"com.android.application\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.instrumentation\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n        \n        versionCode = 1\n        versionName = \"1.0\"\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        multiDexEnabled = true\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n\n    buildTypes {\n        getByName(\"debug\") {\n            isDefault = true\n        }\n    }\n}\n\ndependencies {\n    annotationProcessor(project(\":annotation:compiler\"))\n    implementation(project(\":library\"))\n    implementation(libs.androidx.multidex)\n    implementation(libs.androidx.appcompat)\n\n    androidTestImplementation(project(\":library\"))\n    androidTestImplementation(project(\":mocks\"))\n    androidTestImplementation(project(\":testutil\"))\n    androidTestImplementation(libs.mockito.android)\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.test.rules)\n    androidTestImplementation(libs.androidx.test.core)\n    androidTestImplementation(libs.androidx.espresso.idling)\n    androidTestImplementation(libs.androidx.espresso)\n    androidTestImplementation(libs.truth)\n    androidTestImplementation(libs.junit)\n    androidTestImplementation(libs.androidx.exifinterface)\n    androidTestImplementation(libs.findbugs.jsr305)\n}"
  },
  {
    "path": "instrumentation/gradle.properties",
    "content": ""
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/AsBytesTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ModelGeneratorRule;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(AndroidJUnit4.class)\npublic class AsBytesTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  @Rule public final ModelGeneratorRule modelGeneratorRule = new ModelGeneratorRule();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n\n  private Context context;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test\n  public void loadImageResourceId_asBytes_providesBytesOfBitmap() {\n    byte[] data =\n        concurrency.get(\n            Glide.with(context).as(byte[].class).load(ResourceIds.raw.canonical).submit());\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadBitmap_asBytes_providesBytesOfBitmap() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    byte[] data = concurrency.get(Glide.with(context).as(byte[].class).load(bitmap).submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadBitmapDrawable_asBytes_providesBytesOfBitmap() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    byte[] data =\n        concurrency.get(\n            Glide.with(context)\n                .as(byte[].class)\n                .load(new BitmapDrawable(context.getResources(), bitmap))\n                .submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceId_asBytes_providesBytesOfFrame() {\n    byte[] data =\n        concurrency.get(Glide.with(context).as(byte[].class).load(ResourceIds.raw.video).submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceId_asBytes_withFrameTime_providesBytesOfFrame() {\n    byte[] data =\n        concurrency.get(\n            GlideApp.with(context)\n                .as(byte[].class)\n                .load(ResourceIds.raw.video)\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadVideoFile_asBytes_providesByteOfFrame() throws IOException {\n    byte[] data =\n        concurrency.get(Glide.with(context).as(byte[].class).load(writeVideoToFile()).submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadVideoFile_asBytes_withFrameTime_providesByteOfFrame() throws IOException {\n    byte[] data =\n        concurrency.get(\n            GlideApp.with(context)\n                .as(byte[].class)\n                .load(writeVideoToFile())\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadVideoFilePath_asBytes_providesByteOfFrame() throws IOException {\n    byte[] data =\n        concurrency.get(\n            Glide.with(context)\n                .as(byte[].class)\n                .load(writeVideoToFile().getAbsolutePath())\n                .submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadVideoFilePath_asBytes_withFrameTime_providesByteOfFrame() throws IOException {\n    byte[] data =\n        concurrency.get(\n            GlideApp.with(context)\n                .as(byte[].class)\n                .load(writeVideoToFile().getAbsolutePath())\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadVideoFileUri_asBytes_providesByteOfFrame() throws IOException {\n    byte[] data =\n        concurrency.get(Glide.with(context).as(byte[].class).load(writeVideoToFileUri()).submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  @Test\n  public void loadVideoFileUri_asBytes_withFrameTime_providesByteOfFrame() throws IOException {\n    byte[] data =\n        concurrency.get(\n            GlideApp.with(context)\n                .as(byte[].class)\n                .load(writeVideoToFileUri())\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(data).isNotNull();\n    assertThat(BitmapFactory.decodeByteArray(data, 0, data.length)).isNotNull();\n  }\n\n  private File writeVideoToFile() throws IOException {\n    return modelGeneratorRule.asFile(ResourceIds.raw.video);\n  }\n\n  private Uri writeVideoToFileUri() throws IOException {\n    return Uri.fromFile(writeVideoToFile());\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/AsFileTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.fail;\n\nimport android.content.Context;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ModelGeneratorRule;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.MockModelLoader;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class AsFileTest {\n  private static final String URL = \"https://imgs.xkcd.com/comics/mc_hammer_age.png\";\n\n  @Rule public final ModelGeneratorRule modelGeneratorRule = new ModelGeneratorRule();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Before\n  public void setUp() {\n    MockModelLoader.mock(URL, getData());\n  }\n\n  @Test\n  public void asFile_withUrl_succeeds() {\n    File file = concurrency.get(GlideApp.with(context).asFile().load(URL).submit());\n    assertThat(file).isNotNull();\n  }\n\n  @Test\n  public void asFile_withUrlAndDiskCacheStrategyAutomatic_succeeds() {\n    File file =\n        concurrency.get(\n            GlideApp.with(context)\n                .asFile()\n                .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)\n                .load(URL)\n                .submit());\n    assertThat(file).isNotNull();\n  }\n\n  @Test\n  public void asFile_withUrlAndDiskCacheStrategyData_succeeds() {\n    File file =\n        concurrency.get(\n            GlideApp.with(context)\n                .asFile()\n                .diskCacheStrategy(DiskCacheStrategy.DATA)\n                .load(URL)\n                .submit());\n    assertThat(file).isNotNull();\n  }\n\n  @Test\n  public void asFile_withUrlAndDiskCacheStrategyResource_fails() {\n    try {\n      concurrency.get(\n          GlideApp.with(context)\n              .asFile()\n              .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n              .load(URL)\n              .submit());\n      fail();\n    } catch (RuntimeException e) {\n      // expected.\n    }\n  }\n\n  @Test\n  public void asFile_withUrlAndDiskCacheStrategyAll_fails() {\n    try {\n      concurrency.get(\n          GlideApp.with(context)\n              .asFile()\n              .diskCacheStrategy(DiskCacheStrategy.ALL)\n              .load(URL)\n              .submit());\n      fail();\n    } catch (RuntimeException e) {\n      // Expected.\n    }\n  }\n\n  private InputStream getData() {\n    try {\n      return new ByteArrayInputStream(modelGeneratorRule.asByteArray(ResourceIds.raw.canonical));\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/CachingTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.testutil.BitmapSubject.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.fail;\nimport static org.mockito.AdditionalMatchers.not;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.view.ViewGroup.LayoutParams;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.cache.LruResourceCache;\nimport com.bumptech.glide.load.engine.cache.MemoryCacheAdapter;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.MockGlideExecutor;\nimport com.bumptech.glide.request.FutureTarget;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.test.ResourceIds.raw;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.bumptech.glide.testutil.WaitModelLoader;\nimport com.bumptech.glide.testutil.WaitModelLoader.WaitModel;\nimport com.google.common.truth.Truth;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n/**\n * Tests various aspects of memory and disk caching to verify resources can be retrieved as we\n * expect.\n */\n@RunWith(AndroidJUnit4.class)\npublic class CachingTest {\n  private static final int IMAGE_SIZE_PIXELS = 500;\n  // Store at least 10 500x500 pixel Bitmaps with the ARGB_8888 config to be safe.\n  private static final long CACHE_SIZE_BYTES = IMAGE_SIZE_PIXELS * IMAGE_SIZE_PIXELS * 4 * 10;\n\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n  @Mock private RequestListener<Drawable> requestListener;\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n\n  private Context context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n\n    Glide.init(context, new GlideBuilder().setMemoryCache(new LruResourceCache(CACHE_SIZE_BYTES)));\n  }\n\n  @Test\n  public void submit_withDisabledMemoryCache_andResourceInActiveResources_loadsFromMemory() {\n    Glide.init(context, new GlideBuilder().setMemoryCache(new MemoryCacheAdapter()));\n\n    FutureTarget<Drawable> first = GlideApp.with(context).load(raw.canonical).submit();\n    concurrency.get(first);\n\n    concurrency.get(\n        GlideApp.with(context).load(ResourceIds.raw.canonical).listener(requestListener).submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void submit_withRequestClearedFromMemory_doesNotLoadFromMemory() {\n    Glide.init(context, new GlideBuilder().setMemoryCache(new MemoryCacheAdapter()));\n\n    // Allow the request to be run and GCed without being cleared.\n    concurrency.loadOnOtherThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            FutureTarget<Drawable> first = GlideApp.with(context).load(raw.canonical).submit();\n            concurrency.get(first);\n          }\n        });\n\n    // Wait for the weak reference to be cleared and the request to be removed from active\n    // resources.\n    // De-flake by allowing multiple tries\n    boolean isWeakRefCleared = false;\n    for (int j = 0; j < 100; j++) {\n      Runtime.getRuntime().gc();\n      concurrency.pokeMainThread();\n      try {\n        // Loading again here won't shuffle our resource around because it only changes our\n        // reference count from 1 to 2 and back. The reference we're waiting for will only be\n        // decremented when the target is GCed.\n        Target<Drawable> target =\n            concurrency.wait(\n                GlideApp.with(context)\n                    .load(ResourceIds.raw.canonical)\n                    .onlyRetrieveFromCache(true)\n                    .diskCacheStrategy(DiskCacheStrategy.NONE)\n                    .submit());\n        GlideApp.with(context).clear(target);\n      } catch (RuntimeException e) {\n        // The item has been cleared from active resources.\n        isWeakRefCleared = true;\n        break;\n      }\n    }\n\n    if (!isWeakRefCleared) {\n      fail(\"Failed to clear weak ref.\");\n    }\n\n    concurrency.get(\n        GlideApp.with(context).load(ResourceIds.raw.canonical).listener(requestListener).submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            not(eq(DataSource.MEMORY_CACHE)),\n            anyBoolean());\n  }\n\n  @Test\n  public void submit_withPreviousRequestClearedFromMemory_completesFromDataDiskCache() {\n    // Clearing the future here can race with clearing the EngineResource held on to by EngineJob\n    // while it's notifying callbacks. Forcing all executors to use the same thread avoids the race\n    // by making our clear and EngineJob's clear run on the same thread.\n    GlideExecutor mainThreadExecutor = MockGlideExecutor.newMainThreadExecutor();\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setSourceExecutor(mainThreadExecutor)\n            .setDiskCacheExecutor(mainThreadExecutor)\n            .setAnimationExecutor(mainThreadExecutor));\n\n    FutureTarget<Drawable> future =\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .diskCacheStrategy(DiskCacheStrategy.DATA)\n            .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS);\n    concurrency.get(future);\n    GlideApp.with(context).clear(future);\n\n    clearMemoryCacheOnMainThread();\n\n    concurrency.get(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .diskCacheStrategy(DiskCacheStrategy.DATA)\n            .listener(requestListener)\n            .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS));\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.DATA_DISK_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void submit_withPreviousButNoLongerReferencedIdenticalRequest_completesFromMemoryCache() {\n    // We can't allow any mocks (RequestListner, Target etc) to reference this request or the test\n    // will fail due to the transient strong reference to the request.\n    concurrency.get(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n            .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS));\n\n    // Force the collection of weak references now that the listener/request in the first load is no\n    // longer referenced.\n    Runtime.getRuntime().gc();\n    concurrency.pokeMainThread();\n\n    concurrency.get(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n            .listener(requestListener)\n            .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS));\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void submit_withPreviousButNoLongerReferencedIdenticalRequest_doesNotRecycleBitmap() {\n    // We can't allow any mocks (RequestListener, Target etc) to reference this request or the test\n    // will fail due to the transient strong reference to the request.\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(ResourceIds.raw.canonical)\n                .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n                .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS));\n\n    // Force the collection of weak references now that the listener/request in the first load is no\n    // longer referenced.\n    Runtime.getRuntime().gc();\n    concurrency.pokeMainThread();\n\n    FutureTarget<Bitmap> future =\n        GlideApp.with(context)\n            .asBitmap()\n            .load(ResourceIds.raw.canonical)\n            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n            .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS);\n    concurrency.get(future);\n    Glide.with(context).clear(future);\n\n    clearMemoryCacheOnMainThread();\n\n    assertThat(bitmap).isNotRecycled();\n  }\n\n  @Test\n  public void clearDiskCache_doesNotPreventFutureLoads() {\n    // Clearing the future here can race with clearing the EngineResource held on to by EngineJob\n    // while it's notifying callbacks. Forcing all executors to use the same thread avoids the race\n    // by making our clear and EngineJob's clear run on the same thread.\n    GlideExecutor mainThreadExecutor = MockGlideExecutor.newMainThreadExecutor();\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setSourceExecutor(mainThreadExecutor)\n            .setDiskCacheExecutor(mainThreadExecutor)\n            .setAnimationExecutor(mainThreadExecutor));\n\n    // Load the request once.\n    FutureTarget<Drawable> future =\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .diskCacheStrategy(DiskCacheStrategy.DATA)\n            .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS);\n    concurrency.get(future);\n    // Clear the result from all of our caches.\n    GlideApp.with(context).clear(future);\n    clearMemoryCacheOnMainThread();\n    GlideApp.get(context).clearDiskCache();\n\n    // Load the request a second time into the disk cache.\n    future =\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .diskCacheStrategy(DiskCacheStrategy.DATA)\n            .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS);\n    concurrency.get(future);\n    // Clear the second request from everywhere but the disk cache.\n    GlideApp.with(context).clear(future);\n    clearMemoryCacheOnMainThread();\n\n    // Load the request a third time.\n    concurrency.get(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .listener(requestListener)\n            .diskCacheStrategy(DiskCacheStrategy.DATA)\n            .submit(IMAGE_SIZE_PIXELS, IMAGE_SIZE_PIXELS));\n\n    // Assert that the third request comes from the disk cache (which was populated by the second\n    // request).\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.DATA_DISK_CACHE),\n            anyBoolean());\n  }\n\n  // Tests #2428.\n  @Test\n  public void onlyRetrieveFromCache_withPreviousRequestLoadingFromSource_doesNotBlock() {\n    final WaitModel<Integer> waitModel = WaitModelLoader.waitOn(ResourceIds.raw.canonical);\n\n    FutureTarget<Drawable> loadFromSourceFuture = GlideApp.with(context).load(waitModel).submit();\n\n    FutureTarget<Drawable> onlyFromCacheFuture =\n        GlideApp.with(context).load(waitModel).onlyRetrieveFromCache(true).submit();\n    try {\n      onlyFromCacheFuture.get(1000, TimeUnit.MILLISECONDS);\n      fail(\"Expected only from cache Future to time out\");\n    } catch (InterruptedException | TimeoutException e) {\n      throw new RuntimeException(e);\n    } catch (ExecutionException e) {\n      // Expected.\n    }\n    waitModel.countDown();\n\n    Truth.assertThat(concurrency.get(loadFromSourceFuture)).isNotNull();\n  }\n\n  // Tests #2428.\n  @Test\n  public void submit_withRequestLoadingWithOnlyRetrieveFromCache_andNotInCache_doesNotFail() {\n    // Block the main thread so that we know that both requests will be queued but not started at\n    // the same time.\n    final CountDownLatch blockMainThread = new CountDownLatch(1);\n    new Handler(Looper.getMainLooper())\n        .post(\n            new Runnable() {\n              @Override\n              public void run() {\n                try {\n                  blockMainThread.await();\n                } catch (InterruptedException e) {\n                  throw new RuntimeException(e);\n                }\n              }\n            });\n\n    // Queue the retrieve from cache request first.\n    final Future<Drawable> firstQueuedFuture =\n        GlideApp.with(context).load(ResourceIds.raw.canonical).onlyRetrieveFromCache(true).submit();\n\n    // Then queue the normal request.\n    FutureTarget<Drawable> expectedFuture =\n        GlideApp.with(context).load(ResourceIds.raw.canonical).submit();\n\n    // Run the requests.\n    blockMainThread.countDown();\n\n    // Verify that the request that didn't have retrieve from cache succeeds\n    Truth.assertThat(concurrency.get(expectedFuture)).isNotNull();\n    // The first request only from cache should fail because the item is not in cache.\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            concurrency.get(firstQueuedFuture);\n          }\n        });\n  }\n\n  @Test\n  public void loadIntoView_withoutSkipMemoryCache_loadsFromMemoryCacheIfPresent() {\n    final ImageView imageView = new ImageView(context);\n    imageView.setLayoutParams(new LayoutParams(100, 100));\n\n    concurrency.loadOnMainThread(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .listener(requestListener)\n            .dontTransform(),\n        imageView);\n\n    // Casting avoids a varags array warning.\n    //noinspection rawtypes\n    reset((RequestListener) requestListener);\n\n    // Run on the main thread, since this is already cached, we shouldn't need to try to wait. If we\n    // do end up re-using the old Target, our wait will always timeout anyway if we use\n    // loadOnMainThread. If the load doesn't complete in time, it will be caught by the listener\n    // below, which expects to be called synchronously.\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context)\n                .load(ResourceIds.raw.canonical)\n                .listener(requestListener)\n                .dontTransform()\n                .into(imageView);\n          }\n        });\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadIntoView_withSkipMemoryCacheFalse_loadsFromMemoryCacheIfPresent() {\n    final ImageView imageView = new ImageView(context);\n    imageView.setLayoutParams(new LayoutParams(100, 100));\n\n    concurrency.loadOnMainThread(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .listener(requestListener)\n            .skipMemoryCache(false)\n            .dontTransform(),\n        imageView);\n\n    // Casting avoids a varags array warning.\n    //noinspection rawtypes\n    reset((RequestListener) requestListener);\n\n    // Run on the main thread, since this is already cached, we shouldn't need to try to wait. If we\n    // do end up re-using the old Target, our wait will always timeout anyway if we use\n    // loadOnMainThread. If the load doesn't complete in time, it will be caught by the listener\n    // below, which expects to be called synchronously.\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context)\n                .load(ResourceIds.raw.canonical)\n                .listener(requestListener)\n                .skipMemoryCache(false)\n                .dontTransform()\n                .into(imageView);\n          }\n        });\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadIntoView_withSkipMemoryCache_doesNotLoadFromMemoryCacheIfPresent() {\n    final ImageView imageView = new ImageView(context);\n    imageView.setLayoutParams(new LayoutParams(100, 100));\n\n    concurrency.loadOnMainThread(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .listener(requestListener)\n            .dontTransform()\n            .skipMemoryCache(true),\n        imageView);\n\n    // Casting avoids a varags array warning.\n    //noinspection rawtypes\n    reset((RequestListener) requestListener);\n\n    // If this test fails due to a timeout, it's because we re-used the Target from the previous\n    // request, which breaks the logic in loadOnMainThread that expects a new Target's\n    // onResourceReady callback to be called. This can be confirmed by changing this to\n    // runOnMainThread and verifying that the RequestListener assertion below fails because\n    // the DataSource was from the memory cache.\n    concurrency.loadOnMainThread(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .listener(requestListener)\n            .dontTransform()\n            .skipMemoryCache(true),\n        imageView);\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            not(eq(DataSource.MEMORY_CACHE)),\n            anyBoolean());\n  }\n\n  private void clearMemoryCacheOnMainThread() {\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/CenterCropRegressionTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.BitmapRegressionTester;\nimport com.bumptech.glide.test.CanonicalBitmap;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.RegressionTest;\nimport com.bumptech.glide.test.SplitByCpu;\nimport com.bumptech.glide.test.SplitBySdk;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestName;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\n@RegressionTest\n@SplitByCpu\n@SplitBySdk({24, 21, 16})\npublic class CenterCropRegressionTest {\n  @Rule public final TestName testName = new TestName();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private BitmapRegressionTester bitmapRegressionTester;\n  private Context context;\n  private CanonicalBitmap canonical;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n    bitmapRegressionTester =\n        BitmapRegressionTester.newInstance(getClass(), testName).assumeShouldRun();\n    canonical = new CanonicalBitmap();\n  }\n\n  @Test\n  public void centerCrop_withSquareSmallerThanImage_returnsSquareImage()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .centerCrop()\n                .override(50));\n    assertThat(result.getWidth()).isEqualTo(50);\n    assertThat(result.getHeight()).isEqualTo(50);\n  }\n\n  @Test\n  public void centerCrop_withRectangleSmallerThanImage_returnsRectangularImage()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .centerCrop()\n                .override(60, 70));\n    assertThat(result.getWidth()).isEqualTo(60);\n    assertThat(result.getHeight()).isEqualTo(70);\n  }\n\n  @Test\n  public void centerCrop_withSquareLargerThanImage_returnsUpscaledRectangularImage()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .centerCrop()\n                .override(canonical.getWidth() * 2));\n    assertThat(result.getWidth()).isEqualTo(canonical.getWidth() * 2);\n    assertThat(result.getHeight()).isEqualTo(canonical.getWidth() * 2);\n  }\n\n  @Test\n  public void centerCrop_withRectangleLargerThanImage_returnsUpscaledRectangularImage()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .centerCrop()\n                .override(canonical.getWidth() * 2, canonical.getHeight() * 2));\n    assertThat(result.getWidth()).isEqualTo(canonical.getWidth() * 2);\n    assertThat(result.getHeight()).isEqualTo(canonical.getHeight() * 2);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/CenterInsideRegressionTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.BitmapRegressionTester;\nimport com.bumptech.glide.test.CanonicalBitmap;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.RegressionTest;\nimport com.bumptech.glide.test.SplitByCpu;\nimport com.bumptech.glide.test.SplitBySdk;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestName;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\n@SplitByCpu\n@SplitBySdk({24, 21, 16})\n@RegressionTest\npublic class CenterInsideRegressionTest {\n  @Rule public final TestName testName = new TestName();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private BitmapRegressionTester bitmapRegressionTester;\n  private Context context;\n  private CanonicalBitmap canonical;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n    bitmapRegressionTester =\n        BitmapRegressionTester.newInstance(getClass(), testName).assumeShouldRun();\n    canonical = new CanonicalBitmap();\n  }\n\n  @Test\n  public void centerInside_withSquareSmallerThanImage_returnsImageFitWithinSquare()\n      throws ExecutionException, InterruptedException {\n\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .centerInside()\n                .override(50));\n\n    assertThat(result.getWidth()).isEqualTo(50);\n    assertThat(result.getHeight()).isEqualTo(37);\n  }\n\n  @Test\n  public void centerInside_withSquareLargerThanImage_returnsOriginalImage()\n      throws ExecutionException, InterruptedException {\n    float multiplier = 1.1f;\n    int multipliedWidth = (int) (canonical.getWidth() * multiplier);\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .centerInside()\n                .override(multipliedWidth));\n\n    assertThat(result.getWidth()).isEqualTo(canonical.getWidth());\n    assertThat(result.getHeight()).isEqualTo(canonical.getHeight());\n  }\n\n  @Test\n  public void centerInside_withNarrowRectangle_fitsWithinMaintainingAspectRatio()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .centerInside()\n                .override(canonical.getWidth() / 10, canonical.getHeight()));\n\n    assertThat(result.getWidth()).isEqualTo(canonical.getWidth() / 10);\n    assertThat(result.getHeight()).isEqualTo(canonical.getHeight() / 10);\n  }\n\n  @Test\n  public void centerInside_withShortRectangle_fitsWithinMaintainingAspectRatio()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .centerInside()\n                .override(canonical.getWidth(), canonical.getHeight() / 2));\n\n    assertThat(result.getWidth()).isEqualTo(canonical.getWidth() / 2);\n    assertThat(result.getHeight()).isEqualTo(canonical.getHeight() / 2);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/CircleCropRegressionTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.BitmapRegressionTester;\nimport com.bumptech.glide.test.CanonicalBitmap;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.RegressionTest;\nimport com.bumptech.glide.test.SplitByCpu;\nimport com.bumptech.glide.test.SplitBySdk;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestName;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\n@SplitByCpu\n@SplitBySdk({26, 24, 23, 21, 18, 16})\n@RegressionTest\npublic class CircleCropRegressionTest {\n  @Rule public final TestName testName = new TestName();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private BitmapRegressionTester bitmapRegressionTester;\n  private Context context;\n  private CanonicalBitmap canonical;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n    bitmapRegressionTester =\n        BitmapRegressionTester.newInstance(getClass(), testName).assumeShouldRun();\n    canonical = new CanonicalBitmap();\n  }\n\n  @Test\n  public void circleCrop_withSquareSmallerThanImage_returnsSquaredImage()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .circleCrop()\n                .override(50));\n\n    assertThat(result.getWidth()).isEqualTo(50);\n    assertThat(result.getHeight()).isEqualTo(50);\n  }\n\n  @Test\n  public void circleCrop_withSquareLargerThanImage_returnsUpscaledFitImage()\n      throws ExecutionException, InterruptedException {\n    float multiplier = 1.1f;\n    int multipliedWidth = (int) (canonical.getWidth() * multiplier);\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .circleCrop()\n                .override(multipliedWidth));\n\n    assertThat(result.getWidth()).isEqualTo(multipliedWidth);\n    assertThat(result.getHeight()).isEqualTo(multipliedWidth);\n  }\n\n  @Test\n  public void circleCrop_withNarrowRectangle_cropsWithin()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .circleCrop()\n                .override(canonical.getWidth() / 10, canonical.getHeight()));\n\n    assertThat(result.getWidth()).isEqualTo(canonical.getWidth() / 10);\n    assertThat(result.getHeight()).isEqualTo(canonical.getWidth() / 10);\n  }\n\n  @Test\n  public void circleCrop_withShortRectangle_fitsWithinMaintainingAspectRatio()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .circleCrop()\n                .override(canonical.getWidth(), canonical.getHeight() / 2));\n\n    assertThat(result.getWidth()).isEqualTo(canonical.getHeight() / 2);\n    assertThat(result.getHeight()).isEqualTo(canonical.getHeight() / 2);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/DarkModeTest.java",
    "content": "package com.bumptech.glide;\n\nimport static androidx.test.espresso.Espresso.onIdle;\nimport static com.bumptech.glide.testutil.BitmapSubject.assertThat;\nimport static org.junit.Assume.assumeTrue;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Build.VERSION;\nimport android.os.Build.VERSION_CODES;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewGroup.LayoutParams;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.content.res.AppCompatResources;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.test.core.app.ActivityScenario;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.instrumentation.R;\nimport com.bumptech.glide.load.engine.executor.IdlingGlideRule;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.ForceDarkOrLightModeActivity;\nimport com.google.common.base.Function;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.junit.Before;\nimport org.junit.Ignore;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class DarkModeTest {\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Rule\n  public final IdlingGlideRule idlingGlideRule =\n      IdlingGlideRule.newGlideRule(glideBuilder -> glideBuilder);\n\n  @Before\n  public void before() {\n    // Dark mode wasn't supported prior to Q.\n    assumeTrue(VERSION.SDK_INT >= VERSION_CODES.Q);\n  }\n\n  @Test\n  public void load_withDarkModeActivity_vectorDrawable_usesDarkModeColor() {\n    runActivityDrawableTest(\n        darkModeActivity(),\n        R.drawable.vector_drawable_dark,\n        activity ->\n            Glide.with(activity).load(R.drawable.vector_drawable).override(Target.SIZE_ORIGINAL));\n  }\n\n  @Test\n  public void load_withLightModeActivity_vectorDrawable_usesLightModeColor() {\n    runActivityDrawableTest(\n        lightModeActivity(),\n        R.drawable.vector_drawable_light,\n        activity ->\n            Glide.with(activity).load(R.drawable.vector_drawable).override(Target.SIZE_ORIGINAL));\n  }\n\n  private void runActivityDrawableTest(\n      ActivityScenario<? extends FragmentActivity> scenario,\n      int expectedResource,\n      Function<FragmentActivity, RequestBuilder<Drawable>> glideBuilder) {\n    AtomicReference<Bitmap> result = new AtomicReference<>();\n    try (scenario) {\n      scenario.onActivity(\n          activity -> {\n            ViewGroup container = findContainer(activity);\n            ImageView imageView = newFixedSizeImageView(activity);\n            container.addView(imageView);\n\n            glideBuilder.apply(activity).into(imageView);\n          });\n\n      // This two step process is because setting the Drawable on the ImageView modifies the\n      // drawable in a subsequent frame. If we want our Drawables to produce identical Bitmaps when\n      // drawn to a canvas, we need to set both on the ImageView for at least one frame.\n      onIdle();\n      scenario.onActivity(\n          activity -> {\n            ImageView imageView = findImageView(activity);\n            result.set(drawableToBitmap(imageView.getDrawable()));\n            Drawable expectedDrawable = AppCompatResources.getDrawable(activity, expectedResource);\n            imageView.setImageDrawable(expectedDrawable);\n          });\n      onIdle();\n      scenario.onActivity(\n          activity -> {\n            ImageView imageView = findImageView(activity);\n            Bitmap expected = drawableToBitmap(imageView.getDrawable());\n            assertThat(result.get()).sameAs(expected);\n          });\n    }\n  }\n\n  private static Bitmap drawableToBitmap(Drawable drawable) {\n    int width = drawable.getIntrinsicWidth();\n    int height = drawable.getIntrinsicHeight();\n\n    Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n    Canvas canvas = new Canvas(result);\n    drawable.setBounds(0, 0, width, height);\n    drawable.draw(canvas);\n    canvas.setBitmap(null);\n    return result;\n  }\n\n  @Test\n  public void load_withDarkModeActivity_useDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity -> Glide.with(activity).load(R.drawable.dog).override(Target.SIZE_ORIGINAL));\n  }\n\n  @Test\n  public void load_withDarkModeActivity_afterLoadingWithLightModeActivity_useDarkModeDrawable() {\n    // Load with light mode first.\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        activity -> Glide.with(activity).load(R.drawable.dog).override(Target.SIZE_ORIGINAL));\n\n    // Then again with dark mode to make sure that we do not use the cached resource from the\n    // previous load.\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity -> Glide.with(activity).load(R.drawable.dog).override(Target.SIZE_ORIGINAL));\n  }\n\n  @Test\n  public void\n      load_withDarkModeActivity_afterLoadingWithLightModeActivity_memoryCacheCleared_useDarkModeDrawable() {\n    // Load with light mode first.\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        activity -> Glide.with(activity).load(R.drawable.dog).override(Target.SIZE_ORIGINAL));\n\n    // Then again with dark mode to make sure that we do not use the cached resource from the\n    // previous load.\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity -> {\n          Glide.get(context).clearMemory();\n          return Glide.with(activity).load(R.drawable.dog).override(Target.SIZE_ORIGINAL);\n        });\n  }\n\n  @Test\n  public void load_withDarkModeFragment_usesDarkModeDrawable() {\n    runFragmentTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        fragment -> Glide.with(fragment).load(R.drawable.dog).override(Target.SIZE_ORIGINAL));\n  }\n\n  @Test\n  public void load_withLightModeActivity_usesLightModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        activity -> Glide.with(activity).load(R.drawable.dog).override(Target.SIZE_ORIGINAL));\n  }\n\n  @Test\n  public void load_withLightModeFragment_usesLightModeDrawable() {\n    runFragmentTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        fragment -> Glide.with(fragment).load(R.drawable.dog).override(Target.SIZE_ORIGINAL));\n  }\n\n  @Test\n  public void load_withDarkModeActivity_darkModeTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity ->\n            Glide.with(activity)\n                .load(R.drawable.dog)\n                .override(Target.SIZE_ORIGINAL)\n                .theme(activity.getTheme()));\n  }\n\n  @Test\n  public void loadResourceNameUri_withDarkModeActivity_darkModeTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity ->\n            Glide.with(activity)\n                .load(newResourceNameUri(activity, R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL)\n                .theme(activity.getTheme()));\n  }\n\n  @Test\n  public void loadResourceNameUri_withDarkModeActivity_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity ->\n            Glide.with(activity)\n                .load(newResourceNameUri(activity, R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL));\n  }\n\n  @Test\n  public void\n      loadResourceNameUri_withDarkModeActivity_afterLightModeActivity_usesDarkModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        activity ->\n            Glide.with(activity)\n                .load(newResourceNameUri(activity, R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL));\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity ->\n            Glide.with(activity)\n                .load(newResourceNameUri(activity, R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL));\n  }\n\n  @Test\n  public void loadResourceIdUri_withDarkModeActivity_darkModeTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity ->\n            Glide.with(activity)\n                .load(newResourceIdUri(activity, R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL)\n                .theme(activity.getTheme()));\n  }\n\n  @Test\n  public void loadResourceIdUri_withDarkModeActivity_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        activity ->\n            Glide.with(activity)\n                .load(newResourceIdUri(activity, R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL));\n  }\n\n  private static Uri newResourceNameUri(Context context, int resourceId) {\n    Resources resources = context.getResources();\n    return newResourceUriBuilder(context)\n        .appendPath(resources.getResourceTypeName(resourceId))\n        .appendPath(resources.getResourceEntryName(resourceId))\n        .build();\n  }\n\n  private static Uri newResourceIdUri(Context context, int resourceId) {\n    return newResourceUriBuilder(context).appendPath(String.valueOf(resourceId)).build();\n  }\n\n  private static Uri.Builder newResourceUriBuilder(Context context) {\n    return new Uri.Builder()\n        .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n        .authority(context.getPackageName());\n  }\n\n  @Test\n  public void load_withDarkModeFragment_darkModeTheme_usesDarkModeDrawable() {\n    runFragmentTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        fragment ->\n            Glide.with(fragment)\n                .load(R.drawable.dog)\n                .override(Target.SIZE_ORIGINAL)\n                .theme(fragment.requireActivity().getTheme()));\n  }\n\n  @Test\n  public void loadResourceNameUri_withDarkModeFragment_darkModeTheme_usesDarkModeDrawable() {\n    runFragmentTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        fragment ->\n            Glide.with(fragment)\n                .load(newResourceNameUri(fragment.requireContext(), R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL)\n                .theme(fragment.requireActivity().getTheme()));\n  }\n\n  @Test\n  public void loadResourceIdUri_withDarkModeFragment_darkModeTheme_usesDarkModeDrawable() {\n    runFragmentTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        fragment ->\n            Glide.with(fragment)\n                .load(newResourceIdUri(fragment.requireContext(), R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL)\n                .theme(fragment.requireActivity().getTheme()));\n  }\n\n  @Test\n  public void load_withApplicationContext_darkTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load(R.drawable.dog)\n                .override(Target.SIZE_ORIGINAL)\n                .theme(input.getTheme()));\n  }\n\n  @Ignore(\"TODO(#3751): Consider how to deal with themes applied for application context loads.\")\n  @Test\n  public void load_withApplicationContext_lightTheme_thenDarkTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load(R.drawable.dog)\n                .override(Target.SIZE_ORIGINAL)\n                .theme(input.getTheme()));\n\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load(R.drawable.dog)\n                .override(Target.SIZE_ORIGINAL)\n                .theme(input.getTheme()));\n  }\n\n  @Test\n  public void loadResourceNameUri_withApplicationContext_darkTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load(newResourceNameUri(input.getApplicationContext(), R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL)\n                .theme(input.getTheme()));\n  }\n\n  @Ignore(\"TODO(#3751): Consider how to deal with themes applied for application context loads.\")\n  @Test\n  public void\n      loadResourceNameUri_withApplicationContext_darkTheme_afterLightTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load(newResourceNameUri(input.getApplicationContext(), R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL)\n                .theme(input.getTheme()));\n\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load(newResourceNameUri(input.getApplicationContext(), R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL)\n                .theme(input.getTheme()));\n  }\n\n  @Test\n  public void loadResourceIdUri_withApplicationContext_darkTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load(newResourceIdUri(input.getApplicationContext(), R.drawable.dog))\n                .override(Target.SIZE_ORIGINAL)\n                .theme(input.getTheme()));\n  }\n\n  @Test\n  public void load_withApplicationContext_lightTheme_usesLightModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load(R.drawable.dog)\n                .override(Target.SIZE_ORIGINAL)\n                .theme(input.getTheme()));\n  }\n\n  @Test\n  public void load_withLightModeActivity_lightModeTheme_usesLightModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        activity ->\n            Glide.with(activity)\n                .load(R.drawable.dog)\n                .override(Target.SIZE_ORIGINAL)\n                .theme(activity.getTheme()));\n  }\n\n  @Test\n  public void placeholder_withDarkModeActivity_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input -> Glide.with(input).load((Object) null).placeholder(R.drawable.dog));\n  }\n\n  @Test\n  public void placeholder_withDarkModeFragment_usesDarkModeDrawable() {\n    runFragmentTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input -> Glide.with(input).load((Object) null).placeholder(R.drawable.dog));\n  }\n\n  @Test\n  public void error_withDarkModeActivity_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input -> Glide.with(input).load((Object) null).error(R.drawable.dog));\n  }\n\n  @Test\n  public void error_withDarkModeFragment_usesDarkModeDrawable() {\n    runFragmentTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input -> Glide.with(input).load((Object) null).error(R.drawable.dog));\n  }\n\n  @Test\n  public void fallback_withDarkModeActivity_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input -> Glide.with(input).load((Object) null).fallback(R.drawable.dog));\n  }\n\n  @Test\n  public void fallback_withDarkModeFragment_usesDarkModeDrawable() {\n    runFragmentTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input -> Glide.with(input).load((Object) null).fallback(R.drawable.dog));\n  }\n\n  @Test\n  public void placeholder_withLightModeActivity_usesLightModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        input -> Glide.with(input).load((Object) null).placeholder(R.drawable.dog));\n  }\n\n  @Test\n  public void placeholder_withLightModeFragment_usesLightModeDrawable() {\n    runFragmentTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        input -> Glide.with(input).load((Object) null).placeholder(R.drawable.dog));\n  }\n\n  @Test\n  public void placeholder_withDarkModeActivityAndTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input ->\n            Glide.with(input)\n                .load((Object) null)\n                .theme(input.getTheme())\n                .placeholder(R.drawable.dog));\n  }\n\n  @Test\n  public void placeholder_withLightModeActivityAndTheme_usesLightModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        input ->\n            Glide.with(input)\n                .load((Object) null)\n                .theme(input.getTheme())\n                .placeholder(R.drawable.dog));\n  }\n\n  @Test\n  public void placeholder_withApplicationContext_darkTheme_usesDarkModeDrawable() {\n    runActivityTest(\n        darkModeActivity(),\n        R.raw.dog_dark,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load((Object) null)\n                .theme(input.getTheme())\n                .placeholder(R.drawable.dog));\n  }\n\n  @Test\n  public void placeholder_withApplicationContext_lightTheme_usesLightModeDrawable() {\n    runActivityTest(\n        lightModeActivity(),\n        R.raw.dog_light,\n        input ->\n            Glide.with(input.getApplicationContext())\n                .load((Object) null)\n                .theme(input.getTheme())\n                .placeholder(R.drawable.dog));\n  }\n\n  private ActivityScenario<FragmentActivity> darkModeActivity() {\n    return ActivityScenario.launch(ForceDarkOrLightModeActivity.forceDarkMode(context));\n  }\n\n  private ActivityScenario<FragmentActivity> lightModeActivity() {\n    return ActivityScenario.launch(ForceDarkOrLightModeActivity.forceLightMode(context));\n  }\n\n  private static void runFragmentTest(\n      ActivityScenario<? extends FragmentActivity> scenario,\n      int expectedResource,\n      Function<Fragment, RequestBuilder<Drawable>> requestBuilder) {\n    try (scenario) {\n      scenario.onActivity(\n          activity -> {\n            ImageViewFragment fragment = new ImageViewFragment();\n            activity\n                .getSupportFragmentManager()\n                .beginTransaction()\n                .add(R.id.container, fragment)\n                .commitNowAllowingStateLoss();\n            ViewGroup container = findContainer(activity);\n            ImageView imageView = (ImageView) container.getChildAt(0);\n\n            requestBuilder.apply(fragment).into(imageView);\n          });\n\n      assertImageViewContainerChildHasContent(scenario, expectedResource);\n    }\n  }\n\n  /** Fragment that displays a single fixed size ImageView. */\n  public static final class ImageViewFragment extends Fragment {\n    @Override\n    public View onCreateView(\n        @NonNull LayoutInflater inflater,\n        @Nullable ViewGroup container,\n        @Nullable Bundle savedInstanceState) {\n      return newFixedSizeImageView(getContext());\n    }\n  }\n\n  private static ImageView newFixedSizeImageView(Context context) {\n    ImageView imageView = new ImageView(context);\n    imageView.setLayoutParams(new LayoutParams(200, 200));\n    return imageView;\n  }\n\n  private static void runActivityTest(\n      ActivityScenario<? extends FragmentActivity> scenario,\n      int expectedResource,\n      Function<FragmentActivity, RequestBuilder<Drawable>> glideBuilder) {\n    try (scenario) {\n      scenario.onActivity(\n          activity -> {\n            ViewGroup container = findContainer(activity);\n            ImageView imageView = newFixedSizeImageView(activity);\n            container.addView(imageView);\n\n            glideBuilder.apply(activity).into(imageView);\n          });\n\n      assertImageViewContainerChildHasContent(scenario, expectedResource);\n    }\n  }\n\n  private static void assertImageViewContainerChildHasContent(\n      ActivityScenario<? extends FragmentActivity> scenario, int expectedResource) {\n    onIdle();\n    scenario.onActivity(\n        activity -> {\n          ImageView imageView = findImageView(activity);\n          Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();\n          assertThat(bitmap).sameAs(expectedResource);\n        });\n  }\n\n  private static ImageView findImageView(FragmentActivity activity) {\n    ViewGroup container = findContainer(activity);\n    return (ImageView) container.getChildAt(0);\n  }\n\n  private static ViewGroup findContainer(FragmentActivity activity) {\n    return activity.findViewById(R.id.container);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/DataUriTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.CompressFormat;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.util.Base64;\nimport androidx.core.content.ContextCompat;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.ByteArrayOutputStream;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class DataUriTest {\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Test\n  public void load_withJpegAsDataUriString_returnsBitmap() {\n    Bitmap bitmap =\n        concurrency.get(\n            Glide.with(context).asBitmap().load(getDataUriString(CompressFormat.JPEG)).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withPngDataUriString_returnsBitmap() {\n    Bitmap bitmap =\n        concurrency.get(\n            Glide.with(context).asBitmap().load(getDataUriString(CompressFormat.PNG)).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withJpegAsDataUri_returnsBitmap() {\n    Bitmap bitmap =\n        concurrency.get(\n            Glide.with(context).asBitmap().load(getDataUri(CompressFormat.JPEG)).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withPngAsDataUri_returnsBitmap() {\n    Bitmap bitmap =\n        concurrency.get(\n            Glide.with(context).asBitmap().load(getDataUri(CompressFormat.PNG)).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  private Uri getDataUri(CompressFormat format) {\n    return Uri.parse(getDataUriString(format));\n  }\n\n  private String getDataUriString(CompressFormat format) {\n    String bytes = getBase64BitmapBytes(format);\n    String imageType;\n    switch (format) {\n      case PNG:\n        imageType = \"png\";\n        break;\n      case JPEG:\n        imageType = \"jpeg\";\n        break;\n      case WEBP:\n        imageType = \"webp\";\n        break;\n      default:\n        throw new IllegalArgumentException(\"Unrecognized format: \" + format);\n    }\n\n    String mimeType = \"image/\" + imageType;\n    return \"data:\" + mimeType + \";base64,\" + bytes;\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  private String getBase64BitmapBytes(CompressFormat format) {\n    ByteArrayOutputStream bos = new ByteArrayOutputStream();\n    Drawable drawable =\n        Preconditions.checkNotNull(ContextCompat.getDrawable(context, ResourceIds.raw.canonical));\n    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();\n    bitmap.compress(format, 100, bos);\n    byte[] data = bos.toByteArray();\n    return Base64.encodeToString(data, /* flags= */ 0);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/DownsampleVideoTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.testutil.BitmapSubject.assertThat;\nimport static org.junit.Assume.assumeTrue;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class DownsampleVideoTest {\n  // The dimensions of the test video.\n  private static final int WIDTH = 1080;\n  private static final int HEIGHT = 1920;\n\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Before\n  public void setUp() {\n    assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1);\n  }\n\n  @Test\n  public void loadVideo_downsampleStrategyNone_returnsOriginalVideoDimensions() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(ResourceIds.raw.video)\n                .downsample(DownsampleStrategy.NONE)\n                .submit(10, 10));\n\n    assertThat(bitmap).hasDimensions(WIDTH, HEIGHT);\n  }\n\n  @Test\n  public void loadVideo_downsampleStrategyNone_doesNotUpscale() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(ResourceIds.raw.video)\n                .downsample(DownsampleStrategy.NONE)\n                .submit(WIDTH * 2, HEIGHT * 2));\n\n    assertThat(bitmap).hasDimensions(WIDTH, HEIGHT);\n  }\n\n  @Test\n  public void loadVideo_downsampleDefault_downsamplesVideo() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context).asBitmap().load(ResourceIds.raw.video).submit(10, 10));\n\n    assertThat(bitmap).hasDimensions(10, 18);\n  }\n\n  @Test\n  public void loadVideo_downsampleAtMost_downsamplesToSmallerSize() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.AT_MOST)\n                .load(ResourceIds.raw.video)\n                .submit(540, 959));\n    assertThat(bitmap).hasDimensions(270, 480);\n  }\n\n  @Test\n  public void loadVideo_downsampleAtMost_doesNotUpscale() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.AT_MOST)\n                .load(ResourceIds.raw.video)\n                .submit(WIDTH * 2, HEIGHT * 2));\n    assertThat(bitmap).hasDimensions(WIDTH, HEIGHT);\n  }\n\n  @Test\n  public void loadVideo_downsampleAtLeast_downsamplesToLargerSize() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.AT_LEAST)\n                .load(ResourceIds.raw.video)\n                .submit(270, 481));\n    assertThat(bitmap).hasDimensions(540, 960);\n  }\n\n  @Test\n  public void loadVideo_downsampleAtLeast_doesNotUpscale() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.AT_LEAST)\n                .load(ResourceIds.raw.video)\n                .submit(WIDTH * 2, HEIGHT * 2));\n    assertThat(bitmap).hasDimensions(WIDTH, HEIGHT);\n  }\n\n  @Test\n  public void loadVideo_downsampleCenterInside_downsamplesWithinBox() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.CENTER_INSIDE)\n                .load(ResourceIds.raw.video)\n                .submit(270, 481));\n    assertThat(bitmap).hasDimensions(270, 480);\n  }\n\n  @Test\n  public void loadVideo_downsampleCenterInside_doesNotUpscale() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.CENTER_INSIDE)\n                .load(ResourceIds.raw.video)\n                .submit(WIDTH * 2, HEIGHT * 2));\n    assertThat(bitmap).hasDimensions(WIDTH, HEIGHT);\n  }\n\n  @Test\n  public void loadVideo_downsampleCenterOutside_downsamplesOutsideBox() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.CENTER_OUTSIDE)\n                .load(ResourceIds.raw.video)\n                .submit(270, 481));\n    assertThat(bitmap).hasDimensions(271, 481);\n  }\n\n  @Test\n  public void loadVideo_downsampleCenterOutside_upsacles() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.CENTER_OUTSIDE)\n                .load(ResourceIds.raw.video)\n                .submit(WIDTH * 2, HEIGHT * 2));\n    assertThat(bitmap).hasDimensions(WIDTH * 2, HEIGHT * 2);\n  }\n\n  @Test\n  public void loadVideo_downsampleFitCenter_downsamplesInsideBox() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.FIT_CENTER)\n                .load(ResourceIds.raw.video)\n                .submit(270, 481));\n    assertThat(bitmap).hasDimensions(270, 480);\n  }\n\n  @Test\n  public void loadVideo_downsampleFitCenter_upscales() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.FIT_CENTER)\n                .load(ResourceIds.raw.video)\n                .submit(WIDTH * 2, HEIGHT * 2));\n    assertThat(bitmap).hasDimensions(WIDTH * 2, HEIGHT * 2);\n  }\n\n  @Test\n  public void loadVideo_withSizeOriginal_ignoresDownsampleStrategy() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .downsample(DownsampleStrategy.AT_MOST)\n                .load(ResourceIds.raw.video)\n                .submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL));\n\n    assertThat(bitmap).hasDimensions(WIDTH, HEIGHT);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/DrawableTransformationTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.testutil.BitmapSubject.assertThat;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Handler;\nimport android.os.Looper;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.bitmap.TransformationUtils;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.test.GlideApp;\nimport com.google.common.truth.Truth;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class DrawableTransformationTest {\n  private Context context;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @After\n  public void tearDown() {\n    Glide.get(context).clearDiskCache();\n    Glide.tearDown();\n  }\n\n  @Test\n  public void load_withColorDrawable_sizeOriginal_optionalTransform_returnsColorDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable colorDrawable = new ColorDrawable(Color.RED);\n    Drawable result =\n        Glide.with(context)\n            .load(colorDrawable)\n            .apply(new RequestOptions().optionalCenterCrop())\n            .submit()\n            .get();\n\n    Truth.assertThat(result).isInstanceOf(ColorDrawable.class);\n    assertThat(((ColorDrawable) result).getColor()).isEqualTo(Color.RED);\n  }\n\n  /** Transformations that do nothing can simply return the original Bitmap. */\n  @Test\n  public void load_withColorDrawable_fixedSize_requiredUnitTransform_returnsOriginalDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable colorDrawable = new ColorDrawable(Color.RED);\n\n    Drawable result =\n        Glide.with(context)\n            .load(colorDrawable)\n            .apply(new RequestOptions().centerCrop())\n            .submit(100, 100)\n            .get();\n\n    Truth.assertThat(result).isInstanceOf(ColorDrawable.class);\n    assertThat(((ColorDrawable) result).getColor()).isEqualTo(Color.RED);\n  }\n\n  /**\n   * Transformations that produce a different output color/shape/image etc will end up returning a\n   * {@link Bitmap} based on the original {@link Drawable} but with the transformation applied.\n   */\n  @Test\n  public void load_withColorDrawable_fixedSize_nonUnitRequiredTransform_returnsBitmapDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable colorDrawable = new ColorDrawable(Color.RED);\n\n    Drawable result =\n        Glide.with(context)\n            .load(colorDrawable)\n            .apply(new RequestOptions().circleCrop())\n            .submit(100, 100)\n            .get();\n\n    Bitmap redSquare = Bitmap.createBitmap(100, 100, Config.ARGB_8888);\n    Canvas canvas = new Canvas(redSquare);\n    canvas.drawColor(Color.RED);\n\n    BitmapPool bitmapPool = mock(BitmapPool.class);\n    when(bitmapPool.get(100, 100, Bitmap.Config.ARGB_8888))\n        .thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    Bitmap expected = TransformationUtils.circleCrop(bitmapPool, redSquare, 100, 100);\n\n    Truth.assertThat(result).isInstanceOf(BitmapDrawable.class);\n    Bitmap bitmap = ((BitmapDrawable) result).getBitmap();\n    assertThat(bitmap.getWidth()).isEqualTo(100);\n    assertThat(bitmap.getHeight()).isEqualTo(100);\n    for (int x = 0; x < bitmap.getWidth(); x++) {\n      for (int y = 0; y < bitmap.getHeight(); y++) {\n        assertThat(bitmap.getPixel(x, y)).isEqualTo(expected.getPixel(x, y));\n      }\n    }\n  }\n\n  @Test\n  public void load_withColorDrawable_sizeOriginal_requiredTransform_fails()\n      throws ExecutionException, InterruptedException {\n    final Drawable colorDrawable = new ColorDrawable(Color.RED);\n\n    // The following section is a hack to workaround a weird behavior where a post in RequestManager\n    // can cause a failed request to be started twice in a row if the first attempt happens before.\n    // the post. This seems rather unlikely to happen in real applications and it only occurs when\n    // the request fails unexpectedly, so we're working around this weird behavior in this test.\n    // See #3551.\n\n    // Trigger the Glide application RequestManager to be created.\n    Glide.get(context).getRequestManagerRetriever().get(context);\n    // Wait until it's added as a lifecycle observer.\n    final CountDownLatch latch = new CountDownLatch(1);\n    new Handler(Looper.getMainLooper())\n        .post(\n            new Runnable() {\n              @Override\n              public void run() {\n                latch.countDown();\n              }\n            });\n    latch.await(5, TimeUnit.SECONDS);\n\n    // End hacks.\n\n    assertThrows(\n        ExecutionException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            Glide.with(context)\n                .load(colorDrawable)\n                .apply(new RequestOptions().centerCrop())\n                .submit()\n                .get();\n          }\n        });\n  }\n\n  @Test\n  public void load_withBitmapDrawable_andDoNothingTransformation_doesNotRecycleBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap = Bitmap.createBitmap(100, 200, Config.ARGB_8888);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n\n    Drawable result =\n        GlideApp.with(context)\n            .load(drawable)\n            .fitCenter()\n            .override(bitmap.getWidth(), bitmap.getHeight())\n            .submit()\n            .get();\n\n    assertThat(result).isNotRecycled();\n  }\n\n  @Test\n  public void load_withBitmapDrawable_andFunctionalTransformation_doesNotRecycleBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap = Bitmap.createBitmap(100, 200, Config.ARGB_8888);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n\n    Drawable result =\n        GlideApp.with(context)\n            .load(drawable)\n            .fitCenter()\n            .override(bitmap.getWidth() / 2, bitmap.getHeight() / 2)\n            .submit()\n            .get();\n\n    assertThat(result).isNotRecycled();\n  }\n\n  @Test\n  public void load_withColorDrawable_fixedSize_unitBitmapTransform_recyclesIntermediates()\n      throws ExecutionException, InterruptedException {\n    Drawable colorDrawable = new ColorDrawable(Color.RED);\n\n    int width = 100;\n    int height = 200;\n\n    GlideApp.with(context).load(colorDrawable).fitCenter().override(width, height).submit().get();\n\n    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();\n    // Make sure we didn't put the same Bitmap twice.\n    Bitmap first = bitmapPool.get(width, height, Config.ARGB_8888);\n    Bitmap second = bitmapPool.get(width, height, Config.ARGB_8888);\n\n    assertThat(first).isNotSameInstanceAs(second);\n  }\n\n  @Test\n  public void load_withColorDrawable_fixedSize_functionalBitmapTransform_doesNotRecycleOutput()\n      throws ExecutionException, InterruptedException {\n    Drawable colorDrawable = new ColorDrawable(Color.RED);\n\n    int width = 100;\n    int height = 200;\n\n    Drawable result =\n        GlideApp.with(context)\n            .load(colorDrawable)\n            .circleCrop()\n            .override(width, height)\n            .submit()\n            .get();\n\n    assertThat(result).isNotRecycled();\n\n    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();\n    // Make sure we didn't put the same Bitmap twice.\n    Bitmap first = bitmapPool.get(width, height, Config.ARGB_8888);\n    Bitmap second = bitmapPool.get(width, height, Config.ARGB_8888);\n\n    assertThat(first).isNotSameInstanceAs(second);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/ErrorHandlingTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.EncodeStrategy;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy;\nimport com.bumptech.glide.load.resource.bitmap.BitmapDrawableEncoder;\nimport com.bumptech.glide.request.FutureTarget;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.bumptech.glide.testutil.WaitModelLoader;\nimport com.bumptech.glide.testutil.WaitModelLoader.WaitModel;\nimport java.io.File;\nimport java.util.concurrent.CountDownLatch;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(AndroidJUnit4.class)\npublic class ErrorHandlingTest {\n\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n  @Mock private RequestListener<Drawable> requestListener;\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n\n  private Context context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  private WaitForErrorStrategy initializeGlideWithWaitForErrorStrategy() {\n    WaitForErrorStrategy strategy = new WaitForErrorStrategy();\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setAnimationExecutor(\n                GlideExecutor.newAnimationExecutor(/* threadCount= */ 1, strategy))\n            .setSourceExecutor(GlideExecutor.newSourceExecutor(strategy))\n            .setDiskCacheExecutor(GlideExecutor.newDiskCacheExecutor(strategy)));\n    Glide.get(context)\n        .getRegistry()\n        .prepend(Bitmap.class, new FailEncoder())\n        .prepend(\n            BitmapDrawable.class,\n            new BitmapDrawableEncoder(Glide.get(context).getBitmapPool(), new FailEncoder()));\n\n    return strategy;\n  }\n\n  // ResourceEncoders are expected not to throw and to return true or false. If they do throw, it's\n  // a developer error, so we expect UncaughtThrowableStrategy to be called.\n  @Test\n  public void load_whenEncoderFails_callsUncaughtThrowableStrategy() {\n    WaitForErrorStrategy strategy = initializeGlideWithWaitForErrorStrategy();\n\n    concurrency.get(\n        Glide.with(context).load(ResourceIds.raw.canonical).listener(requestListener).submit());\n\n    // Writing to the disk cache and therefore the exception caused by our FailEncoder may happen\n    // after the request completes, so we should wait for the expected error explicitly.\n    ConcurrencyHelper.waitOnLatch(strategy.latch);\n    assertThat(strategy.error).isEqualTo(FailEncoder.TO_THROW);\n\n    verify(requestListener, never())\n        .onLoadFailed(\n            any(GlideException.class),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            anyBoolean());\n  }\n\n  @Test\n  public void load_whenLoadSucceeds_butEncoderFails_doesNotCallOnLoadFailed() {\n    WaitForErrorStrategy strategy = initializeGlideWithWaitForErrorStrategy();\n\n    concurrency.get(\n        Glide.with(context).load(ResourceIds.raw.canonical).listener(requestListener).submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            any(DataSource.class),\n            anyBoolean());\n    verify(requestListener, never())\n        .onLoadFailed(\n            any(GlideException.class),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            anyBoolean());\n  }\n\n  @Test\n  public void clearRequest_withError_afterPrimaryFails_clearsErrorRequest() {\n    WaitModel<Integer> errorModel = WaitModelLoader.waitOn(ResourceIds.raw.canonical);\n\n    FutureTarget<Drawable> target =\n        Glide.with(context)\n            .load((Object) null)\n            .error(Glide.with(context).load(errorModel).listener(requestListener))\n            .submit();\n\n    Glide.with(context).clear(target);\n    errorModel.countDown();\n\n    // Make sure any pending requests run.\n    concurrency.pokeMainThread();\n    Glide.tearDown();\n    // Make sure that any callbacks posted back to the main thread run.\n    concurrency.pokeMainThread();\n  }\n\n  private static final class WaitForErrorStrategy implements UncaughtThrowableStrategy {\n    final CountDownLatch latch = new CountDownLatch(1);\n    @Nullable Throwable error = null;\n\n    @Override\n    public void handle(Throwable t) {\n      if (error != null) {\n        throw new IllegalArgumentException(\"Received second error\", t);\n      }\n      error = t;\n      latch.countDown();\n    }\n  }\n\n  private static final class FailEncoder implements ResourceEncoder<Bitmap> {\n\n    static final RuntimeException TO_THROW = new RuntimeException();\n\n    @NonNull\n    @Override\n    public EncodeStrategy getEncodeStrategy(@NonNull Options options) {\n      return EncodeStrategy.TRANSFORMED;\n    }\n\n    @Override\n    public boolean encode(\n        @NonNull Resource<Bitmap> data, @NonNull File file, @NonNull Options options) {\n      throw TO_THROW;\n    }\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/ExternallyClearedDiskCacheTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.mock;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.engine.cache.DiskCache.Factory;\nimport com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.test.ResourceIds.raw;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.File;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n// Tests #2465.\n@RunWith(AndroidJUnit4.class)\npublic class ExternallyClearedDiskCacheTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private Context context;\n  private File cacheDir;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n    cacheDir = context.getCacheDir();\n  }\n\n  @After\n  public void tearDown() {\n    // Force us to wait until Glide's threads shut down.\n    Glide.tearDown();\n    deleteRecursively(cacheDir);\n  }\n\n  @Test\n  public void clearDiskCache_afterOpeningDiskCache_andDeleteDirectoryOutsideGlide_doesNotThrow() {\n    DiskCache cache = DiskLruCacheWrapper.create(cacheDir, 1024 * 1024);\n    cache.get(mock(Key.class));\n    deleteRecursively(cacheDir);\n    cache.clear();\n  }\n\n  @Test\n  public void get_afterDeleteDirectoryOutsideGlideAndClose_doesNotThrow() {\n    DiskCache cache = DiskLruCacheWrapper.create(cacheDir, 1024 * 1024);\n    cache.get(mock(Key.class));\n    deleteRecursively(cacheDir);\n    cache.clear();\n\n    cache.get(mock(Key.class));\n  }\n\n  @Test\n  public void loadFromCache_afterDiskCacheDeletedAndCleared_doesNotFail() {\n    final DiskCache cache = DiskLruCacheWrapper.create(cacheDir, 1024 * 1024);\n    cache.get(mock(Key.class));\n    deleteRecursively(cacheDir);\n    cache.clear();\n\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setDiskCache(\n                new Factory() {\n                  @Override\n                  public DiskCache build() {\n                    return cache;\n                  }\n                }));\n\n    Drawable drawable =\n        concurrency.get(Glide.with(context).load(ResourceIds.raw.canonical).submit());\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void loadFromCache_afterDiskCacheDeleted_doesNotFail() {\n    final DiskCache cache = DiskLruCacheWrapper.create(cacheDir, 1024 * 1024);\n    cache.get(mock(Key.class));\n    deleteRecursively(cacheDir);\n\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setDiskCache(\n                new Factory() {\n                  @Override\n                  public DiskCache build() {\n                    return cache;\n                  }\n                }));\n\n    Drawable drawable = concurrency.get(Glide.with(context).load(raw.canonical).submit());\n    assertThat(drawable).isNotNull();\n  }\n\n  private static void deleteRecursively(File file) {\n    if (file.isDirectory()) {\n      File[] files = file.listFiles();\n      if (files != null) {\n        for (File f : files) {\n          deleteRecursively(f);\n        }\n      }\n    }\n    if (!file.delete() && file.exists()) {\n      throw new RuntimeException(\"Failed to delete: \" + file);\n    }\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/FitCenterRegressionTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.BitmapRegressionTester;\nimport com.bumptech.glide.test.CanonicalBitmap;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.RegressionTest;\nimport com.bumptech.glide.test.SplitByCpu;\nimport com.bumptech.glide.test.SplitBySdk;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.rules.TestName;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\n@SplitByCpu\n@SplitBySdk({24, 23, 21, 19, 18, 16})\n@RegressionTest\npublic class FitCenterRegressionTest {\n  @Rule public final TestName testName = new TestName();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private BitmapRegressionTester bitmapRegressionTester;\n  private Context context;\n  private CanonicalBitmap canonical;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n    bitmapRegressionTester =\n        BitmapRegressionTester.newInstance(getClass(), testName).assumeShouldRun();\n    canonical = new CanonicalBitmap();\n  }\n\n  @Test\n  public void fitCenter_withSquareSmallerThanImage_returnsImageFitWithinSquare()\n      throws ExecutionException, InterruptedException {\n\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context).asBitmap().load(canonical.getBitmap()).fitCenter().override(50));\n\n    assertThat(result.getWidth()).isEqualTo(50);\n    assertThat(result.getHeight()).isEqualTo(37);\n  }\n\n  @Test\n  public void fitCenter_withSquareLargerThanImage_returnsUpscaledSquare()\n      throws ExecutionException, InterruptedException {\n    float multiplier = 1.1f;\n    int multipliedWidth = (int) (canonical.getWidth() * multiplier);\n    int multipliedHeight = (int) (canonical.getHeight() * multiplier);\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .fitCenter()\n                .override(multipliedWidth));\n\n    assertThat(result.getWidth()).isEqualTo(multipliedWidth);\n    assertThat(result.getHeight()).isEqualTo(multipliedHeight);\n  }\n\n  @Test\n  public void fitCenter_withNarrowRectangle_fitsWithinMaintainingAspectRatio()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .fitCenter()\n                .override(canonical.getWidth() / 10, canonical.getHeight()));\n\n    assertThat(result.getWidth()).isEqualTo(canonical.getWidth() / 10);\n    assertThat(result.getHeight()).isEqualTo(canonical.getHeight() / 10);\n  }\n\n  @Test\n  public void fitCenter_withShortRectangle_fitsWithinMaintainingAspectRatio()\n      throws ExecutionException, InterruptedException {\n    Bitmap result =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .fitCenter()\n                .override(canonical.getWidth(), canonical.getHeight() / 2));\n\n    assertThat(result.getWidth()).isEqualTo(canonical.getWidth() / 2);\n    assertThat(result.getHeight()).isEqualTo(canonical.getHeight() / 2);\n  }\n\n  @Test\n  public void fitCenter_withHugeRectangle_throwsOOM()\n      throws ExecutionException, InterruptedException {\n    float multiplier = Integer.MAX_VALUE / (canonical.getWidth() * canonical.getHeight() * 2);\n    final int overrideWidth = (int) multiplier * canonical.getWidth();\n    final int overrideHeight = (int) multiplier * canonical.getHeight();\n\n    assertThrows(\n        ExecutionException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonical.getBitmap())\n                .fitCenter()\n                .override(overrideWidth, overrideHeight)\n                .submit()\n                .get();\n          }\n        });\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/LargeImageTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.model.UnitModelLoader;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.util.Objects;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class LargeImageTest {\n  @Rule public final TestRule tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Test\n  public void loadLargeJpeg_asByteArray_succeeds() throws IOException {\n    byte[] data = getLargeImageBytes();\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(data).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void loadLargeJpeg_asByteBuffer_succeeds() throws IOException {\n    // Using UnitModelLoader lets us mimic loading the ByteBuffer from some other data type, which\n    // reduces the scope of our test.\n    Glide.get(context)\n        .getRegistry()\n        .append(\n            ByteBuffer.class, ByteBuffer.class, UnitModelLoader.Factory.<ByteBuffer>getInstance());\n\n    ByteBuffer buffer = ByteBuffer.wrap(getLargeImageBytes());\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(buffer).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  private byte[] getLargeImageBytes() throws IOException {\n    Resources resources = context.getResources();\n    int resourceId = ResourceIds.raw.canonical_large;\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(resources.getResourcePackageName(resourceId))\n            .appendPath(resources.getResourceTypeName(resourceId))\n            .appendPath(resources.getResourceEntryName(resourceId))\n            .build();\n\n    InputStream is = Objects.requireNonNull(context.getContentResolver().openInputStream(uri));\n    ByteArrayOutputStream os = new ByteArrayOutputStream();\n    byte[] buffer = new byte[1024 * 1024 * 5];\n    int read;\n    while ((read = is.read(buffer, 0, buffer.length)) != -1) {\n      os.write(buffer, 0, read);\n    }\n    return os.toByteArray();\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/LoadAnimatedImageResourceTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assume.assumeTrue;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.graphics.drawable.AnimatedImageDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Build;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.IOException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.MockitoAnnotations;\n\n/**\n * Tests that Glide is able to load animated images (WebP and AVIF) stored in resources and loaded\n * as {@link android.graphics.drawable.AnimatedImageDrawable}s when the underlying Android platform\n * supports it.\n */\n@RunWith(AndroidJUnit4.class)\npublic class LoadAnimatedImageResourceTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n\n  private Context context;\n\n  private static final boolean IS_ANIMATED_WEBP_SUPPORTED =\n      Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;\n  private static final boolean IS_ANIMATED_AVIF_SUPPORTED =\n      Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test\n  public void loadAnimatedImageResourceId_fromInt_decodesAnimatedImageDrawable_Webp() {\n    assumeTrue(IS_ANIMATED_WEBP_SUPPORTED);\n    Drawable frame =\n        concurrency.get(Glide.with(context).load(ResourceIds.raw.animated_webp).submit());\n\n    assertThat(frame).isNotNull();\n    assertThat(frame).isInstanceOf(AnimatedImageDrawable.class);\n  }\n\n  @Test\n  public void loadAnimatedImageResourceId_fromInt_decodesAnimatedImageDrawable_Avif() {\n    assumeTrue(IS_ANIMATED_AVIF_SUPPORTED);\n    Drawable frame =\n        concurrency.get(Glide.with(context).load(ResourceIds.raw.animated_avif).submit());\n\n    assertThat(frame).isNotNull();\n    assertThat(frame).isInstanceOf(AnimatedImageDrawable.class);\n  }\n\n  @Test\n  public void loadAnimatedImageUri_fromId_decodesAnimatedImageDrawable_Webp() {\n    assumeTrue(IS_ANIMATED_WEBP_SUPPORTED);\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(context.getPackageName())\n            .path(String.valueOf(ResourceIds.raw.animated_webp))\n            .build();\n\n    Drawable frame = concurrency.get(GlideApp.with(context).load(uri).submit());\n\n    assertThat(frame).isNotNull();\n    assertThat(frame).isInstanceOf(AnimatedImageDrawable.class);\n  }\n\n  @Test\n  public void loadAnimatedImageUri_fromId_decodesAnimatedImageDrawable_Avif() {\n    assumeTrue(IS_ANIMATED_AVIF_SUPPORTED);\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(context.getPackageName())\n            .path(String.valueOf(ResourceIds.raw.animated_avif))\n            .build();\n\n    Drawable frame = concurrency.get(GlideApp.with(context).load(uri).submit());\n\n    assertThat(frame).isNotNull();\n    assertThat(frame).isInstanceOf(AnimatedImageDrawable.class);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/LoadAssetUriTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.IOException;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.MockitoAnnotations;\n\n/**\n * Tests that Glide is able to load images and videos stored in assets and loaded as {@link\n * android.content.res.AssetFileDescriptor}s.\n */\n@RunWith(AndroidJUnit4.class)\npublic class LoadAssetUriTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private static final String VIDEO_ASSET_NAME = \"video.mp4\";\n  private static final String IMAGE_ASSET_NAME = \"canonical.jpg\";\n\n  private Context context;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test\n  public void loadVideoAssetUri_decodesFrame() {\n    Uri uri = Uri.parse(assetNameToUri(VIDEO_ASSET_NAME));\n\n    Drawable frame = concurrency.get(GlideApp.with(context).load(uri).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoAssetUri_asBitmap_decodesFrame() {\n    Uri uri = Uri.parse(assetNameToUri(VIDEO_ASSET_NAME));\n\n    Bitmap frame = concurrency.get(GlideApp.with(context).asBitmap().load(uri).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoAssetUri_withFrame_decodesFrame() {\n    Uri uri = Uri.parse(assetNameToUri(VIDEO_ASSET_NAME));\n\n    Bitmap frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(uri)\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoAssetUriString_decodesFrame() {\n    Uri uri = Uri.parse(assetNameToUri(VIDEO_ASSET_NAME));\n\n    Bitmap frame = concurrency.get(GlideApp.with(context).asBitmap().load(uri.toString()).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoAssetUriString_withFrame_decodesFrame() {\n    Uri uri = Uri.parse(assetNameToUri(VIDEO_ASSET_NAME));\n\n    Bitmap frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(uri.toString())\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadImageAssetUri_decodesImage() {\n    Uri uri = Uri.parse(assetNameToUri(IMAGE_ASSET_NAME));\n\n    Drawable image = concurrency.get(GlideApp.with(context).load(uri).submit());\n\n    assertThat(image).isNotNull();\n  }\n\n  @Test\n  public void loadImageAssetUri_asBitmap_decodesImage() {\n    Uri uri = Uri.parse(assetNameToUri(IMAGE_ASSET_NAME));\n\n    Bitmap image = concurrency.get(GlideApp.with(context).asBitmap().load(uri).submit());\n\n    assertThat(image).isNotNull();\n  }\n\n  @Test\n  public void loadImageAssetUriString_decodesImage() {\n    Uri uri = Uri.parse(assetNameToUri(IMAGE_ASSET_NAME));\n\n    Bitmap image = concurrency.get(GlideApp.with(context).asBitmap().load(uri.toString()).submit());\n\n    assertThat(image).isNotNull();\n  }\n\n  private static String assetNameToUri(String assetName) {\n    return \"file:///android_asset/\" + assetName;\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/LoadBitmapTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.verify;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.drawable.Drawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;\nimport com.bumptech.glide.load.engine.cache.LruResourceCache;\nimport com.bumptech.glide.load.engine.cache.MemoryCacheAdapter;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.MockGlideExecutor;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.bumptech.glide.util.Util;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(AndroidJUnit4.class)\npublic class LoadBitmapTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  @Mock private RequestListener<Bitmap> bitmapListener;\n  @Mock private RequestListener<Drawable> drawableListener;\n\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private Context context;\n  private GlideBuilder glideBuilder;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n\n    // Clearing the future here can race with clearing the EngineResource held on to by EngineJob\n    // while it's notifying callbacks. Forcing all executors to use the same thread avoids the race\n    // by making our clear and EngineJob's clear run on the same thread.\n    GlideExecutor mainThreadExecutor = MockGlideExecutor.newMainThreadExecutor();\n    glideBuilder =\n        new GlideBuilder()\n            .setSourceExecutor(mainThreadExecutor)\n            .setDiskCacheExecutor(mainThreadExecutor)\n            .setAnimationExecutor(mainThreadExecutor);\n  }\n\n  @Test\n  public void clearFromRequestBuilder_asDrawable_withLoadedBitmap_doesNotRecycleBitmap() {\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setMemoryCache(new MemoryCacheAdapter())\n            .setBitmapPool(new BitmapPoolAdapter()));\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    Target<Drawable> target =\n        concurrency.wait(GlideApp.with(context).asDrawable().load(bitmap).submit(100, 100));\n    Glide.with(context).clear(target);\n\n    // Allow Glide's resource recycler to run on the main thread.\n    concurrency.pokeMainThread();\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void transformFromRequestBuilder_asDrawable_withLoadedBitmap_doesNotRecycleBitmap() {\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setMemoryCache(new MemoryCacheAdapter())\n            .setBitmapPool(new BitmapPoolAdapter()));\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    concurrency.wait(\n        GlideApp.with(context).asDrawable().load(bitmap).centerCrop().submit(100, 100));\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void clearFromRequestManager_withLoadedBitmap_doesNotRecycleBitmap() {\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setMemoryCache(new MemoryCacheAdapter())\n            .setBitmapPool(new BitmapPoolAdapter()));\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    Target<Drawable> target =\n        concurrency.wait(GlideApp.with(context).load(bitmap).submit(100, 100));\n    Glide.with(context).clear(target);\n\n    // Allow Glide's resource recycler to run on the main thread.\n    concurrency.pokeMainThread();\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void transformFromRequestManager_withLoadedBitmap_doesNotRecycleBitmap() {\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setMemoryCache(new MemoryCacheAdapter())\n            .setBitmapPool(new BitmapPoolAdapter()));\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    concurrency.wait(GlideApp.with(context).load(bitmap).centerCrop().submit(100, 100));\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void clearFromRequestBuilder_withLoadedBitmap_asBitmap_doesNotRecycleBitmap() {\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setMemoryCache(new MemoryCacheAdapter())\n            .setBitmapPool(new BitmapPoolAdapter()));\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    Target<Bitmap> target =\n        concurrency.wait(GlideApp.with(context).asBitmap().load(bitmap).submit(100, 100));\n    Glide.with(context).clear(target);\n\n    // Allow Glide's resource recycler to run on the main thread.\n    concurrency.pokeMainThread();\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void transformFromRequestBuilder_withLoadedBitmap_asBitmap_doesNotRecycleBitmap() {\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setMemoryCache(new MemoryCacheAdapter())\n            .setBitmapPool(new BitmapPoolAdapter()));\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    concurrency.wait(GlideApp.with(context).asBitmap().load(bitmap).centerCrop().submit(100, 100));\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void loadFromRequestManager_withBitmap_doesNotLoadFromDiskCache() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))\n            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));\n    Target<Drawable> target =\n        concurrency.wait(GlideApp.with(context).load(bitmap).centerCrop().submit(100, 100));\n    Glide.with(context).clear(target);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .load(bitmap)\n            .centerCrop()\n            .listener(drawableListener)\n            .submit(100, 100));\n\n    verify(drawableListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_asDrawable_withBitmap_doesNotLoadFromDiskCache() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))\n            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));\n    Target<Drawable> target =\n        concurrency.wait(\n            GlideApp.with(context).asDrawable().load(bitmap).centerCrop().submit(100, 100));\n    Glide.with(context).clear(target);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .load(bitmap)\n            .centerCrop()\n            .listener(drawableListener)\n            .submit(100, 100));\n\n    verify(drawableListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_asDrawable_withBitmapAndStrategyBeforeLoad_notFromCache() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))\n            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));\n    Target<Drawable> target =\n        concurrency.wait(\n            GlideApp.with(context)\n                .asDrawable()\n                .diskCacheStrategy(DiskCacheStrategy.ALL)\n                .load(bitmap)\n                .centerCrop()\n                .submit(100, 100));\n    Glide.with(context).clear(target);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .load(bitmap)\n            .centerCrop()\n            .listener(drawableListener)\n            .submit(100, 100));\n\n    verify(drawableListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_asBitmap_withBitmap_doesNotLoadFromDiskCache() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))\n            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));\n    Target<Bitmap> target =\n        concurrency.wait(\n            GlideApp.with(context).asBitmap().load(bitmap).centerCrop().submit(100, 100));\n    Glide.with(context).clear(target);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .asBitmap()\n            .load(bitmap)\n            .centerCrop()\n            .listener(bitmapListener)\n            .submit(100, 100));\n\n    verify(bitmapListener)\n        .onResourceReady(\n            ArgumentMatchers.<Bitmap>any(),\n            any(),\n            ArgumentMatchers.<Target<Bitmap>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_asBitmap_withBitmapAndStrategyBeforeLoad_notFromCache() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))\n            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));\n\n    Target<Bitmap> target =\n        concurrency.wait(\n            GlideApp.with(context)\n                .asBitmap()\n                .diskCacheStrategy(DiskCacheStrategy.ALL)\n                .load(bitmap)\n                .centerCrop()\n                .submit(100, 100));\n    Glide.with(context).clear(target);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .asBitmap()\n            .load(bitmap)\n            .centerCrop()\n            .listener(bitmapListener)\n            .submit(100, 100));\n\n    verify(bitmapListener)\n        .onResourceReady(\n            ArgumentMatchers.<Bitmap>any(),\n            any(),\n            ArgumentMatchers.<Target<Bitmap>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/LoadBytesTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.test.GlideOptions.skipMemoryCacheOf;\nimport static com.bumptech.glide.testutil.BitmapSubject.assertThat;\nimport static org.junit.Assert.fail;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.verify;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.CompressFormat;\nimport android.graphics.Bitmap.Config;\nimport android.graphics.BitmapFactory;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.widget.AbsListView.LayoutParams;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.MockGlideExecutor;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.google.common.io.ByteStreams;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(AndroidJUnit4.class)\npublic class LoadBytesTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n\n  @Mock private RequestListener<Drawable> requestListener;\n\n  private Context context;\n  private ImageView imageView;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n\n    imageView = new ImageView(context);\n    int[] dimensions = getCanonicalDimensions();\n    imageView.setLayoutParams(new LayoutParams(/* w= */ dimensions[0], /* h= */ dimensions[1]));\n\n    // Writes to the resource disk cache run in a non-blocking manner after the Target is notified.\n    // Unless we enforce a single threaded executor, the encode task races with our second decode\n    // task, causing the test to sometimes fail (when the second resource is started after the\n    // encode and loaded from the disk cache) and sometimes succeed (when the second resource is\n    // started before the encode and loads from source).\n    ExecutorService executor = Executors.newSingleThreadExecutor();\n    GlideExecutor glideExecutor = MockGlideExecutor.newTestExecutor(executor);\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setAnimationExecutor(glideExecutor)\n            .setDiskCacheExecutor(glideExecutor)\n            .setSourceExecutor(glideExecutor));\n  }\n\n  @Test\n  public void loadFromRequestManager_intoImageView_withDifferentByteArrays_loadsDifferentImages()\n      throws IOException {\n    final byte[] canonicalBytes = getCanonicalBytes();\n    final byte[] modifiedBytes = getModifiedBytes();\n\n    concurrency.loadOnMainThread(Glide.with(context).load(canonicalBytes), imageView);\n    Bitmap firstBitmap = copyFromImageViewDrawable(imageView);\n\n    concurrency.loadOnMainThread(Glide.with(context).load(modifiedBytes), imageView);\n    Bitmap secondBitmap = copyFromImageViewDrawable(imageView);\n\n    // This assertion alone doesn't catch the case where the second Bitmap is loaded from the result\n    // cache of the data from the first Bitmap.\n    assertThat(firstBitmap).isNotSameInstanceAs(secondBitmap);\n\n    Bitmap expectedCanonicalBitmap =\n        BitmapFactory.decodeByteArray(canonicalBytes, /* offset= */ 0, canonicalBytes.length);\n    assertThat(firstBitmap).sameAs(expectedCanonicalBitmap);\n\n    Bitmap expectedModifiedBitmap =\n        BitmapFactory.decodeByteArray(modifiedBytes, /* offset= */ 0, modifiedBytes.length);\n    assertThat(secondBitmap).sameAs(expectedModifiedBitmap);\n  }\n\n  @Test\n  public void loadFromRequestBuilder_intoImageView_withDifferentByteArrays_loadsDifferentImages()\n      throws IOException {\n    final byte[] canonicalBytes = getCanonicalBytes();\n    final byte[] modifiedBytes = getModifiedBytes();\n\n    concurrency.loadOnMainThread(\n        GlideApp.with(context).asDrawable().load(canonicalBytes), imageView);\n    Bitmap firstBitmap = copyFromImageViewDrawable(imageView);\n\n    concurrency.loadOnMainThread(\n        GlideApp.with(context).asDrawable().load(modifiedBytes), imageView);\n    Bitmap secondBitmap = copyFromImageViewDrawable(imageView);\n\n    // This assertion alone doesn't catch the case where the second Bitmap is loaded from the result\n    // cache of the data from the first Bitmap.\n    assertThat(firstBitmap).isNotSameInstanceAs(secondBitmap);\n\n    Bitmap expectedCanonicalBitmap =\n        BitmapFactory.decodeByteArray(canonicalBytes, /* offset= */ 0, canonicalBytes.length);\n    assertThat(firstBitmap).sameAs(expectedCanonicalBitmap);\n\n    Bitmap expectedModifiedBitmap =\n        BitmapFactory.decodeByteArray(modifiedBytes, /* offset= */ 0, modifiedBytes.length);\n    assertThat(secondBitmap).sameAs(expectedModifiedBitmap);\n  }\n\n  @Test\n  public void requestManager_intoImageView_withSameByteArrayAndMemoryCacheEnabled_loadsFromMemory()\n      throws IOException {\n    final byte[] canonicalBytes = getCanonicalBytes();\n    concurrency.loadOnMainThread(\n        Glide.with(context).load(canonicalBytes).apply(skipMemoryCacheOf(false)), imageView);\n\n    Glide.with(context).clear(imageView);\n\n    concurrency.loadOnMainThread(\n        Glide.with(context)\n            .load(canonicalBytes)\n            .listener(requestListener)\n            .apply(skipMemoryCacheOf(false)),\n        imageView);\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void requestBuilder_intoImageView_withSameByteArrayAndMemoryCacheEnabled_loadsFromMemory()\n      throws IOException {\n    final byte[] canonicalBytes = getCanonicalBytes();\n    concurrency.loadOnMainThread(\n        Glide.with(context).asDrawable().load(canonicalBytes).apply(skipMemoryCacheOf(false)),\n        imageView);\n\n    Glide.with(context).clear(imageView);\n\n    concurrency.loadOnMainThread(\n        Glide.with(context)\n            .asDrawable()\n            .load(canonicalBytes)\n            .listener(requestListener)\n            .apply(skipMemoryCacheOf(false)),\n        imageView);\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestManager_withSameByteArray_validDiskCacheStrategy_returnsFromDiskCache()\n      throws IOException {\n    byte[] data = getCanonicalBytes();\n    Target<Drawable> target =\n        concurrency.wait(\n            GlideApp.with(context)\n                .load(data)\n                .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n                .submit());\n    GlideApp.with(context).clear(target);\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .load(data)\n            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n            .listener(requestListener)\n            .submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.RESOURCE_DISK_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_withSameByteArray_validDiskCacheStrategy_returnsFromDiskCache()\n      throws IOException {\n    byte[] data = getCanonicalBytes();\n    Target<Drawable> target =\n        concurrency.wait(\n            GlideApp.with(context)\n                .asDrawable()\n                .load(data)\n                .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n                .submit());\n    GlideApp.with(context).clear(target);\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .asDrawable()\n            .load(data)\n            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n            .listener(requestListener)\n            .submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.RESOURCE_DISK_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestManager_withSameByteArray_memoryCacheEnabled_returnsFromCache()\n      throws IOException {\n    byte[] data = getCanonicalBytes();\n    Target<Drawable> target =\n        concurrency.wait(GlideApp.with(context).load(data).skipMemoryCache(false).submit());\n    GlideApp.with(context).clear(target);\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .load(data)\n            .skipMemoryCache(false)\n            .listener(requestListener)\n            .submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_withSameByteArray_memoryCacheEnabled_returnsFromCache()\n      throws IOException {\n    byte[] data = getCanonicalBytes();\n    Target<Drawable> target =\n        concurrency.wait(\n            GlideApp.with(context).asDrawable().load(data).skipMemoryCache(false).submit());\n    GlideApp.with(context).clear(target);\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .asDrawable()\n            .load(data)\n            .skipMemoryCache(false)\n            .listener(requestListener)\n            .submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestManager_withSameByteArray_returnsFromLocal() throws IOException {\n    byte[] data = getCanonicalBytes();\n    Target<Drawable> target = concurrency.wait(GlideApp.with(context).load(data).submit());\n    GlideApp.with(context).clear(target);\n\n    concurrency.wait(GlideApp.with(context).load(data).listener(requestListener).submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_withSameByteArray_returnsFromLocal() throws IOException {\n    byte[] data = getCanonicalBytes();\n    Target<Drawable> target =\n        concurrency.wait(GlideApp.with(context).asDrawable().load(data).submit());\n    GlideApp.with(context).clear(target);\n\n    concurrency.wait(\n        GlideApp.with(context).asDrawable().load(data).listener(requestListener).submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestManager_withSameByteArrayAndMissingFromMemory_returnsFromLocal()\n      throws IOException {\n    byte[] data = getCanonicalBytes();\n    Target<Drawable> target = concurrency.wait(GlideApp.with(context).load(data).submit());\n    GlideApp.with(context).clear(target);\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(GlideApp.with(context).load(data).listener(requestListener).submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_withSameByteArrayAndMissingFromMemory_returnsFromLocal()\n      throws IOException {\n    byte[] data = getCanonicalBytes();\n    Target<Drawable> target =\n        concurrency.wait(GlideApp.with(context).asDrawable().load(data).submit());\n    GlideApp.with(context).clear(target);\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context).asDrawable().load(data).listener(requestListener).submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromBuilder_withDiskCacheStrategySetBeforeLoad_doesNotOverrideDiskCacheStrategy()\n      throws IOException {\n    byte[] data = getCanonicalBytes();\n    concurrency.wait(\n        GlideApp.with(context)\n            .asDrawable()\n            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n            .load(data)\n            .submit());\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .asDrawable()\n            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)\n            .listener(requestListener)\n            .load(data)\n            .submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.RESOURCE_DISK_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromBuilder_withSkipMemoryCacheSetBeforeLoad_doesNotOverrideSkipMemoryCache()\n      throws IOException {\n    byte[] data = getCanonicalBytes();\n    concurrency.wait(\n        GlideApp.with(context).asDrawable().skipMemoryCache(false).load(data).submit());\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .asDrawable()\n            .skipMemoryCache(false)\n            .listener(requestListener)\n            .load(data)\n            .submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromBuilder_withDataDiskCacheStrategy_returnsFromSource() throws IOException {\n    byte[] data = getCanonicalBytes();\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .asDrawable()\n            .diskCacheStrategy(DiskCacheStrategy.DATA)\n            .load(data)\n            .submit());\n\n    concurrency.wait(\n        GlideApp.with(context)\n            .asDrawable()\n            .diskCacheStrategy(DiskCacheStrategy.DATA)\n            .skipMemoryCache(true)\n            .load(data)\n            .listener(requestListener)\n            .submit());\n\n    verify(requestListener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.DATA_DISK_CACHE),\n            anyBoolean());\n  }\n\n  private Bitmap copyFromImageViewDrawable(ImageView imageView) {\n    if (imageView.getDrawable() == null) {\n      fail(\"Drawable unexpectedly null\");\n    }\n\n    // Glide mutates Bitmaps, so it's possible that a Bitmap loaded into a View in one place may\n    // be re-used to load a different image later. Create a defensive copy just in case.\n    return Bitmap.createBitmap(((BitmapDrawable) imageView.getDrawable()).getBitmap());\n  }\n\n  private int[] getCanonicalDimensions() throws IOException {\n    byte[] canonicalBytes = getCanonicalBytes();\n    Bitmap bitmap =\n        BitmapFactory.decodeByteArray(canonicalBytes, /* offset= */ 0, canonicalBytes.length);\n    return new int[] {bitmap.getWidth(), bitmap.getHeight()};\n  }\n\n  private byte[] getModifiedBytes() throws IOException {\n    int[] dimensions = getCanonicalDimensions();\n    Bitmap bitmap = Bitmap.createBitmap(dimensions[0], dimensions[1], Config.ARGB_8888);\n    ByteArrayOutputStream os = new ByteArrayOutputStream();\n    bitmap.compress(CompressFormat.PNG, /* quality= */ 100, os);\n    return os.toByteArray();\n  }\n\n  private byte[] getCanonicalBytes() throws IOException {\n    int resourceId = ResourceIds.raw.canonical;\n    Resources resources = context.getResources();\n    InputStream is = resources.openRawResource(resourceId);\n    return ByteStreams.toByteArray(is);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/LoadDrawableTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.verify;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;\nimport com.bumptech.glide.load.engine.cache.LruResourceCache;\nimport com.bumptech.glide.load.engine.cache.MemoryCacheAdapter;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.MockGlideExecutor;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.bumptech.glide.util.Util;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(AndroidJUnit4.class)\npublic class LoadDrawableTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n\n  @Mock private RequestListener<Drawable> listener;\n\n  private Context context;\n  private GlideExecutor executor;\n  private GlideBuilder glideBuilder;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    executor = MockGlideExecutor.newMainThreadExecutor();\n\n    context = ApplicationProvider.getApplicationContext();\n    glideBuilder =\n        new GlideBuilder()\n            .setAnimationExecutor(executor)\n            .setSourceExecutor(executor)\n            .setDiskCacheExecutor(executor);\n  }\n\n  @Test\n  public void clear_withLoadedBitmapDrawable_doesNotRecycleBitmap() {\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new MemoryCacheAdapter())\n            .setBitmapPool(new BitmapPoolAdapter()));\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n    Target<Drawable> target =\n        concurrency.wait(GlideApp.with(context).load(drawable).submit(100, 100));\n    Glide.with(context).clear(target);\n\n    // Allow Glide's resource recycler to run on the main thread.\n    concurrency.pokeMainThread();\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void transform_withLoadedBitmapDrawable_doesNotRecycleBitmap() {\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new MemoryCacheAdapter())\n            .setBitmapPool(new BitmapPoolAdapter()));\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n    concurrency.wait(GlideApp.with(context).load(drawable).centerCrop().submit(100, 100));\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void loadFromRequestManager_withBitmap_doesNotLoadFromDiskCache() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))\n            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));\n    Target<Drawable> target =\n        concurrency.wait(GlideApp.with(context).load(drawable).centerCrop().submit(100, 100));\n    Glide.with(context).clear(target);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context).load(drawable).centerCrop().listener(listener).submit(100, 100));\n\n    verify(listener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_asDrawable_withBitmap_doesNotLoadFromDiskCache() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))\n            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));\n    Target<Drawable> target =\n        concurrency.wait(\n            GlideApp.with(context).asDrawable().load(drawable).centerCrop().submit(100, 100));\n    Glide.with(context).clear(target);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context).load(drawable).centerCrop().listener(listener).submit(100, 100));\n\n    verify(listener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void loadFromRequestBuilder_asDrawable_withBitmapAndStrategyBeforeLoad_notFromCache() {\n    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n    Glide.init(\n        context,\n        glideBuilder\n            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))\n            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));\n    Target<Drawable> target =\n        concurrency.wait(\n            GlideApp.with(context)\n                .asDrawable()\n                .diskCacheStrategy(DiskCacheStrategy.ALL)\n                .load(drawable)\n                .centerCrop()\n                .submit(100, 100));\n    Glide.with(context).clear(target);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.get(context).clearMemory();\n          }\n        });\n\n    concurrency.wait(\n        GlideApp.with(context).load(drawable).centerCrop().listener(listener).submit(100, 100));\n\n    verify(listener)\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/LoadResourcesWithDownsamplerTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assume.assumeTrue;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.ColorSpace;\nimport android.net.Uri;\nimport android.os.Build;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.load.resource.bitmap.Downsampler;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.bumptech.glide.util.Util;\nimport java.io.ByteArrayOutputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Locale;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * On API 26, decoding a variety of different images can cause {@link BitmapFactory} with {@link\n * BitmapFactory.Options#inJustDecodeBounds} set to {@code true} to set {@link\n * BitmapFactory.Options#outConfig} to null instead of a valid value, even though the image can be\n * decoded successfully. Glide can mask these failures by decoding some image sources (notably\n * including resource ids) using other data types and decoders.\n *\n * <p>This test ensures that we've worked around the framework issue by loading a variety of images\n * and image types without the normal fallback behavior.\n */\n@RunWith(AndroidJUnit4.class)\npublic class LoadResourcesWithDownsamplerTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Test\n  public void loadJpegResource_withNoOtherLoaders_decodesResource() {\n    Glide.get(context)\n        .getRegistry()\n        .prepend(Object.class, InputStream.class, new FakeModelLoader<>(ResourceIds.raw.canonical));\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(new Object()).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void loadWideGamutJpegResource_withNoOtherLoaders_decodesWideGamutBitmap() {\n    assumeTrue(\n        \"Wide gamut is only available on O+\", Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);\n    Glide.get(context)\n        .getRegistry()\n        .prepend(\n            Object.class, InputStream.class, new FakeModelLoader<>(ResourceIds.raw.webkit_logo_p3));\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(new Object()).submit());\n    assertThat(bitmap).isNotNull();\n    assertThat(bitmap.getConfig()).isEqualTo(Bitmap.Config.RGBA_F16);\n\n    // The exact value here depends on the emulator / device we're running on. On Pixel devices and\n    // emulators it'll return DISPLAY_P3. On 'generic' emulators and some other devices, it'll\n    // return LINEAR_EXTENDED_SRGB. It's unclear how else we can assert correctly based on the\n    // device type, so I've just left this is isAnyOf for now.\n    assertThat(bitmap.getColorSpace())\n        .isAnyOf(\n            ColorSpace.get(ColorSpace.Named.DISPLAY_P3),\n            ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB));\n  }\n\n  @Test\n  public void loadOpaquePngResource_withNoOtherLoaders_decodesResource() {\n    Glide.get(context)\n        .getRegistry()\n        .prepend(\n            Object.class, InputStream.class, new FakeModelLoader<>(ResourceIds.raw.canonical_png));\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(new Object()).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void loadTransparentPngResource_withNoOtherLoaders_decodesResource() {\n    Glide.get(context)\n        .getRegistry()\n        .prepend(\n            Object.class,\n            InputStream.class,\n            new FakeModelLoader<>(ResourceIds.raw.canonical_transparent_png));\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(new Object()).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void loadTransparentGifResource_withNoOtherLoaders_decodesResource() {\n    Glide.get(context)\n        .getRegistry()\n        .prepend(\n            Object.class,\n            InputStream.class,\n            new FakeModelLoader<>(ResourceIds.raw.transparent_gif));\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(new Object()).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void loadTransparentGifResource_asHardware_withNoOtherLoaders_decodesResource()\n      throws InterruptedException {\n    assumeTrue(\n        \"Hardware Bitmaps are only supported on P+\",\n        Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);\n    // enableHardwareBitmaps must be called on the main thread.\n    final CountDownLatch latch = new CountDownLatch(1);\n    Util.postOnUiThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.enableHardwareBitmaps();\n            latch.countDown();\n          }\n        });\n    latch.await(5, TimeUnit.SECONDS);\n\n    Glide.get(context)\n        .getRegistry()\n        .prepend(\n            Object.class,\n            InputStream.class,\n            new FakeModelLoader<>(ResourceIds.raw.transparent_gif));\n\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .set(Downsampler.ALLOW_HARDWARE_CONFIG, true)\n                .format(DecodeFormat.PREFER_ARGB_8888)\n                .load(new Object())\n                .submit());\n    assertThat(bitmap).isNotNull();\n    assertThat(bitmap.getConfig()).isEqualTo(Bitmap.Config.HARDWARE);\n  }\n\n  @Test\n  public void loadTransparentGifResource_withNoOtherLoaders_fromBytes_decodesResource() {\n    byte[] data = getBytes(ResourceIds.raw.transparent_gif);\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(data).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void loadOpaqueGifResource_withNoOtherLoaders_decodesResource() {\n    Glide.get(context)\n        .getRegistry()\n        .prepend(\n            Object.class, InputStream.class, new FakeModelLoader<>(ResourceIds.raw.opaque_gif));\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(new Object()).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void loadOpaqueGifResource_asBytes_decodesResource() {\n    byte[] data = getBytes(ResourceIds.raw.opaque_gif);\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(data).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void loadOpaqueGifResource_asHardware_withNoOtherLoaders_decodesResource() {\n    assumeTrue(\n        \"Hardware Bitmaps are only supported on P+\",\n        Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);\n\n    Glide.get(context)\n        .getRegistry()\n        .prepend(\n            Object.class, InputStream.class, new FakeModelLoader<>(ResourceIds.raw.opaque_gif));\n\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                // Allow HARDWARE Bitmaps.\n                .format(DecodeFormat.PREFER_ARGB_8888)\n                .load(new Object())\n                .submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  private byte[] getBytes(int resourceId) {\n    ByteArrayOutputStream os = new ByteArrayOutputStream();\n    InputStream is = null;\n    try {\n      is = context.getResources().openRawResource(resourceId);\n      byte[] buffer = new byte[1024 * 1024];\n      int read;\n      while ((read = is.read(buffer)) != -1) {\n        os.write(buffer, 0, read);\n      }\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    } finally {\n      if (is != null) {\n        try {\n          is.close();\n        } catch (IOException e) {\n          // Ignored;\n        }\n      }\n    }\n\n    return os.toByteArray();\n  }\n\n  private class FakeModelLoader<T>\n      implements ModelLoader<T, InputStream>, ModelLoaderFactory<T, InputStream> {\n\n    private final int resourceId;\n\n    FakeModelLoader(int resourceId) {\n      this.resourceId = resourceId;\n    }\n\n    @androidx.annotation.Nullable\n    @Override\n    public LoadData<InputStream> buildLoadData(\n        @NonNull Object o, int width, int height, @NonNull Options options) {\n      return new LoadData<>(new ObjectKey(o), new Fetcher());\n    }\n\n    @Override\n    public boolean handles(@NonNull Object o) {\n      return true;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<T, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return this;\n    }\n\n    @Override\n    public void teardown() {}\n\n    private final class Fetcher implements DataFetcher<InputStream> {\n      private InputStream inputStream;\n\n      @Override\n      public void loadData(\n          @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {\n        inputStream = getInputStreamForResource(context, resourceId);\n        callback.onDataReady(inputStream);\n      }\n\n      private InputStream getInputStreamForResource(Context context, @DrawableRes int resourceId) {\n        Resources resources = context.getResources();\n        try {\n          Uri parse =\n              Uri.parse(\n                  String.format(\n                      Locale.US,\n                      \"%s://%s/%s/%s\",\n                      ContentResolver.SCHEME_ANDROID_RESOURCE,\n                      resources.getResourcePackageName(resourceId),\n                      resources.getResourceTypeName(resourceId),\n                      resources.getResourceEntryName(resourceId)));\n          return context.getContentResolver().openInputStream(parse);\n        } catch (Resources.NotFoundException | FileNotFoundException e) {\n          throw new IllegalArgumentException(\"Resource ID \" + resourceId + \" not found\", e);\n        }\n      }\n\n      @Override\n      public void cleanup() {\n        InputStream local = inputStream;\n        if (local != null) {\n          try {\n            local.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n      }\n\n      @Override\n      public void cancel() {\n        // Do nothing.\n      }\n\n      @NonNull\n      @Override\n      public Class<InputStream> getDataClass() {\n        return InputStream.class;\n      }\n\n      @NonNull\n      @Override\n      public DataSource getDataSource() {\n        return DataSource.LOCAL;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/LoadVideoResourceTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.IOException;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.MockitoAnnotations;\n\n/**\n * Tests that Glide is able to load videos stored in resources and loaded as {@link\n * android.content.res.AssetFileDescriptor}s.\n */\n@RunWith(AndroidJUnit4.class)\npublic class LoadVideoResourceTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n\n  private Context context;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test\n  public void loadVideoResourceId_fromInt_decodesFrame() {\n    Drawable frame = concurrency.get(Glide.with(context).load(ResourceIds.raw.video).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceId_fromInt_withFrameTime_decodesFrame() {\n    Drawable frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .load(ResourceIds.raw.video)\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  // Testing boxed integer.\n  @SuppressWarnings(\"UnnecessaryBoxing\")\n  @Test\n  public void loadVideoResourceId_fromInteger_decodesFrame() {\n    Drawable frame =\n        concurrency.get(Glide.with(context).load(new Integer(ResourceIds.raw.video)).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  // Testing boxed integer.\n  @SuppressWarnings(\"UnnecessaryBoxing\")\n  @Test\n  public void loadVideoResourceId_fromInteger_withFrameTime_decodesFrame() {\n    Drawable frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .load(new Integer(ResourceIds.raw.video))\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceId_asBitmap_decodesFrame() {\n    Bitmap frame =\n        concurrency.get(Glide.with(context).asBitmap().load(ResourceIds.raw.video).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceId_asBitmap_withFrameTime_decodesFrame() {\n    Bitmap frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(ResourceIds.raw.video)\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUri_fromId_decodesFrame() {\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(context.getPackageName())\n            .path(String.valueOf(ResourceIds.raw.video))\n            .build();\n\n    Drawable frame = concurrency.get(GlideApp.with(context).load(uri).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUri_asBitmap_fromId_decodesFrame() {\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(context.getPackageName())\n            .path(String.valueOf(ResourceIds.raw.video))\n            .build();\n\n    Bitmap frame = concurrency.get(GlideApp.with(context).asBitmap().load(uri).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUri_fromId_withFrame_decodesFrame() {\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(context.getPackageName())\n            .path(String.valueOf(ResourceIds.raw.video))\n            .build();\n\n    Bitmap frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(uri)\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUriString_fromId_decodesFrame() {\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(context.getPackageName())\n            .path(String.valueOf(ResourceIds.raw.video))\n            .build();\n\n    Bitmap frame = concurrency.get(GlideApp.with(context).asBitmap().load(uri.toString()).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUriString_fromId_withFrame_decodesFrame() {\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(context.getPackageName())\n            .path(String.valueOf(ResourceIds.raw.video))\n            .build();\n\n    Bitmap frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(uri.toString())\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUri_fromName_decodesFrame() {\n    Resources resources = context.getResources();\n    int resourceId = ResourceIds.raw.video;\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(resources.getResourcePackageName(resourceId))\n            .appendPath(resources.getResourceTypeName(resourceId))\n            .appendPath(resources.getResourceEntryName(resourceId))\n            .build();\n\n    Drawable frame = concurrency.get(GlideApp.with(context).load(uri).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUri_asBitmap_fromName_decodesFrame() {\n    Resources resources = context.getResources();\n    int resourceId = ResourceIds.raw.video;\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(resources.getResourcePackageName(resourceId))\n            .appendPath(resources.getResourceTypeName(resourceId))\n            .appendPath(resources.getResourceEntryName(resourceId))\n            .build();\n\n    Bitmap frame = concurrency.get(GlideApp.with(context).asBitmap().load(uri).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUri_fromName_withFrame_decodesFrame() {\n    Resources resources = context.getResources();\n    int resourceId = ResourceIds.raw.video;\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(resources.getResourcePackageName(resourceId))\n            .appendPath(resources.getResourceTypeName(resourceId))\n            .appendPath(resources.getResourceEntryName(resourceId))\n            .build();\n\n    Bitmap frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(uri)\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUriString_fromName_decodesFrame() {\n    Resources resources = context.getResources();\n    int resourceId = ResourceIds.raw.video;\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(resources.getResourcePackageName(resourceId))\n            .appendPath(resources.getResourceTypeName(resourceId))\n            .appendPath(resources.getResourceEntryName(resourceId))\n            .build();\n\n    Bitmap frame = concurrency.get(GlideApp.with(context).asBitmap().load(uri.toString()).submit());\n\n    assertThat(frame).isNotNull();\n  }\n\n  @Test\n  public void loadVideoResourceUriString_fromName_withFrame_decodesFrame() {\n    Resources resources = context.getResources();\n    int resourceId = ResourceIds.raw.video;\n    Uri uri =\n        new Uri.Builder()\n            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n            .authority(resources.getResourcePackageName(resourceId))\n            .appendPath(resources.getResourceTypeName(resourceId))\n            .appendPath(resources.getResourceEntryName(resourceId))\n            .build();\n\n    Bitmap frame =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(uri.toString())\n                .frame(TimeUnit.SECONDS.toMicros(1))\n                .submit());\n\n    assertThat(frame).isNotNull();\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/MultiRequestTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.CompressFormat;\nimport android.graphics.Bitmap.Config;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.CustomTarget;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.test.ModelGeneratorRule;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class MultiRequestTest {\n  private final Context context = ApplicationProvider.getApplicationContext();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  @Rule public final ModelGeneratorRule modelGeneratorRule = new ModelGeneratorRule();\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n\n  @Test\n  public void thumbnail_onResourceReady_forPrimary_isComplete_whenRequestListenerIsCalled()\n      throws IOException, InterruptedException {\n\n    // Make sure the requests complete in the same order\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setSourceExecutor(GlideExecutor.newSourceBuilder().setThreadCount(1).build()));\n\n    AtomicBoolean isPrimaryRequestComplete = new AtomicBoolean(false);\n    CountDownLatch countDownLatch = new CountDownLatch(1);\n\n    RequestBuilder<Drawable> request =\n        Glide.with(context)\n            .load(newImageFile())\n            .thumbnail(Glide.with(context).load(newImageFile()))\n            .listener(\n                new RequestListener<>() {\n                  @Override\n                  public boolean onLoadFailed(\n                      @Nullable GlideException e,\n                      Object model,\n                      @NonNull Target<Drawable> target,\n                      boolean isFirstResource) {\n                    return false;\n                  }\n\n                  @Override\n                  public boolean onResourceReady(\n                      @NonNull Drawable resource,\n                      @NonNull Object model,\n                      Target<Drawable> target,\n                      @NonNull DataSource dataSource,\n                      boolean isFirstResource) {\n                    isPrimaryRequestComplete.set(target.getRequest().isComplete());\n                    countDownLatch.countDown();\n                    return false;\n                  }\n                });\n    concurrency.runOnMainThread(() -> request.into(new DoNothingTarget()));\n\n    assertThat(countDownLatch.await(3, TimeUnit.SECONDS)).isTrue();\n    assertThat(isPrimaryRequestComplete.get()).isTrue();\n  }\n\n  @Test\n  public void thumbnail_onLoadFailed_forPrimary_isNotRunningOrComplete_whenRequestListenerIsCalled()\n      throws IOException, InterruptedException {\n\n    // Make sure the requests complete in the same order\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setSourceExecutor(GlideExecutor.newSourceBuilder().setThreadCount(1).build()));\n\n    AtomicBoolean isNeitherRunningNorComplete = new AtomicBoolean(false);\n    CountDownLatch countDownLatch = new CountDownLatch(1);\n\n    int missingResourceId = 123;\n    RequestBuilder<Drawable> requestBuilder =\n        Glide.with(context)\n            .load(missingResourceId)\n            .thumbnail(Glide.with(context).load(newImageFile()))\n            .listener(\n                new RequestListener<>() {\n                  @Override\n                  public boolean onLoadFailed(\n                      @Nullable GlideException e,\n                      Object model,\n                      @NonNull Target<Drawable> target,\n                      boolean isFirstResource) {\n                    Request request = target.getRequest();\n                    isNeitherRunningNorComplete.set(!request.isComplete() && !request.isRunning());\n                    countDownLatch.countDown();\n                    return false;\n                  }\n\n                  @Override\n                  public boolean onResourceReady(\n                      @NonNull Drawable resource,\n                      @NonNull Object model,\n                      Target<Drawable> target,\n                      @NonNull DataSource dataSource,\n                      boolean isFirstResource) {\n                    return false;\n                  }\n                });\n    concurrency.runOnMainThread(() -> requestBuilder.into(new DoNothingTarget()));\n\n    assertThat(countDownLatch.await(3, TimeUnit.SECONDS)).isTrue();\n    assertThat(isNeitherRunningNorComplete.get()).isTrue();\n  }\n\n  private File newImageFile() throws IOException {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);\n    Canvas canvas = new Canvas(bitmap);\n    canvas.drawColor(Color.RED);\n    File result = temporaryFolder.newFile();\n    try (OutputStream os = new BufferedOutputStream(new FileOutputStream(result))) {\n      bitmap.compress(CompressFormat.JPEG, 75, os);\n    }\n    return result;\n  }\n\n  // We don't store or do anything with the resource, so we don't need to do anything to release it\n  // in onLoadCleared.\n  private static final class DoNothingTarget extends CustomTarget<Drawable> {\n    @Override\n    public void onResourceReady(\n        @NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {}\n\n    @Override\n    public void onLoadCleared(@Nullable Drawable placeholder) {}\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.request.RequestOptions.bitmapTransform;\nimport static com.bumptech.glide.request.RequestOptions.centerCropTransform;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.content.pm.ResolveInfo;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.rules.TestName;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class NonBitmapDrawableResourcesTest {\n  @Rule public final TestName testName = new TestName();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Test\n  public void load_withBitmapResourceId_asDrawable_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable = Glide.with(context).load(android.R.drawable.star_big_off).submit().get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withBitmapResourceId_asDrawable_withTransformation_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context)\n            .load(android.R.drawable.star_big_off)\n            .apply(centerCropTransform())\n            .submit()\n            .get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withBitmapResourceId_asBitmap_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context).asBitmap().load(android.R.drawable.star_big_off).submit().get();\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withBitmapAliasResourceId_asDrawable_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable = Glide.with(context).load(ResourceIds.drawable.bitmap_alias).submit().get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withBitmapAliasResourceId_asDrawable_withTransformation_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context)\n            .load(ResourceIds.drawable.bitmap_alias)\n            .apply(centerCropTransform())\n            .submit()\n            .get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withBitmapAliasResourceId_asBitmap_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context).asBitmap().load(ResourceIds.drawable.bitmap_alias).submit().get();\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withShapeDrawableResourceId_asDrawable_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context).load(ResourceIds.drawable.shape_drawable).submit().get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withShapeDrawableResourceId_asDrawable_withTransformation_sizeOriginal_fails()\n      throws ExecutionException, InterruptedException {\n    assertThrows(\n        ExecutionException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            Glide.with(context)\n                .load(ResourceIds.drawable.shape_drawable)\n                .apply(centerCropTransform())\n                .submit()\n                .get();\n          }\n        });\n  }\n\n  @Test\n  public void load_withShapeDrawableResourceId_asDrawable_withTransformation_validSize_succeeds()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context)\n            .load(ResourceIds.drawable.shape_drawable)\n            .apply(bitmapTransform(new RoundedCorners(10)))\n            .submit(100, 200)\n            .get();\n    assertThat(drawable).isNotNull();\n    assertThat(drawable.getIntrinsicWidth()).isEqualTo(100);\n    assertThat(drawable.getIntrinsicHeight()).isEqualTo(200);\n  }\n\n  @Test\n  public void load_withShapeDrawableResourceId_asBitmap_withSizeOriginal_fails()\n      throws ExecutionException, InterruptedException {\n    assertThrows(\n        ExecutionException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            Glide.with(context).asBitmap().load(ResourceIds.drawable.shape_drawable).submit().get();\n          }\n        });\n  }\n\n  @Test\n  public void load_withShapeDrawableResourceId_asBitmap_withValidSize_returnsNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context)\n            .asBitmap()\n            .load(ResourceIds.drawable.shape_drawable)\n            .submit(100, 200)\n            .get();\n    assertThat(bitmap).isNotNull();\n    assertThat(bitmap.getWidth()).isEqualTo(100);\n    assertThat(bitmap.getHeight()).isEqualTo(200);\n  }\n\n  @Test\n  public void load_withShapeDrawableResourceId_asBitmap_withValidSizeAndTransform_nonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context)\n            .asBitmap()\n            .load(ResourceIds.drawable.shape_drawable)\n            .apply(centerCropTransform())\n            .submit(100, 200)\n            .get();\n    assertThat(bitmap).isNotNull();\n    assertThat(bitmap.getWidth()).isEqualTo(100);\n    assertThat(bitmap.getHeight()).isEqualTo(200);\n  }\n\n  @Test\n  public void load_withStateListDrawableResourceId_asDrawable_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context).load(ResourceIds.drawable.state_list_drawable).submit().get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withStateListDrawableResourceId_asDrawable_withTransformation_nonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context)\n            .load(ResourceIds.drawable.state_list_drawable)\n            .apply(centerCropTransform())\n            .submit()\n            .get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withStateListDrawableResourceId_asBitmap_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context)\n            .asBitmap()\n            .load(ResourceIds.drawable.state_list_drawable)\n            .submit()\n            .get();\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withStateListDrawableResourceId_asBitmap_withTransformation_nonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context)\n            .asBitmap()\n            .load(ResourceIds.drawable.state_list_drawable)\n            .apply(centerCropTransform())\n            .submit()\n            .get();\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withVectorDrawableResourceId_asDrawable_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context).load(ResourceIds.drawable.vector_drawable).submit().get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withVectorDrawableResourceId_asDrawable_withTransformation_nonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context)\n            .load(ResourceIds.drawable.vector_drawable)\n            .apply(centerCropTransform())\n            .submit()\n            .get();\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withVectorDrawableResourceId_asBitmap_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context).asBitmap().load(ResourceIds.drawable.vector_drawable).submit().get();\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withVectorDrawableResourceId_asBitmap_withTransformation_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context)\n            .asBitmap()\n            .load(ResourceIds.drawable.vector_drawable)\n            .apply(centerCropTransform())\n            .submit()\n            .get();\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withNinePatchResourceId_asDrawable_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context).load(ResourceIds.drawable.googlelogo_color_120x44dp).submit().get();\n\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withNinePatchResourceId_asDrawable_withTransformation_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException {\n    Drawable drawable =\n        Glide.with(context)\n            .load(ResourceIds.drawable.googlelogo_color_120x44dp)\n            .apply(centerCropTransform())\n            .submit()\n            .get();\n\n    assertThat(drawable).isNotNull();\n  }\n\n  @Test\n  public void load_withNinePatchResourceId_asBitmap_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context)\n            .asBitmap()\n            .load(ResourceIds.drawable.googlelogo_color_120x44dp)\n            .submit()\n            .get();\n\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withNinePatchResourceId_asBitmap_withTransformation_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    Bitmap bitmap =\n        Glide.with(context)\n            .asBitmap()\n            .load(ResourceIds.drawable.googlelogo_color_120x44dp)\n            .apply(centerCropTransform())\n            .submit()\n            .get();\n\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withApplicationIconResourceIdUri_asDrawable_producesNonNullDrawable()\n      throws NameNotFoundException, ExecutionException, InterruptedException {\n    for (String packageName : getInstalledPackages()) {\n      int iconResourceId = getResourceId(packageName);\n\n      Uri uri =\n          new Uri.Builder()\n              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n              .authority(packageName)\n              .path(String.valueOf(iconResourceId))\n              .build();\n\n      Drawable drawable = Glide.with(context).load(uri).submit().get();\n      assertThat(drawable).isNotNull();\n    }\n  }\n\n  @Test\n  public void load_withApplicationIconResourceIdUri_asDrawable_withTransformation_nonNullDrawable()\n      throws NameNotFoundException, ExecutionException, InterruptedException {\n    for (String packageName : getInstalledPackages()) {\n      int iconResourceId = getResourceId(packageName);\n\n      Uri uri =\n          new Uri.Builder()\n              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n              .authority(packageName)\n              .path(String.valueOf(iconResourceId))\n              .build();\n\n      Drawable drawable = Glide.with(context).load(uri).apply(centerCropTransform()).submit().get();\n      assertThat(drawable).isNotNull();\n    }\n  }\n\n  @Test\n  public void load_withApplicationIconResourceIdUri_asBitmap_producesNonNullBitmap()\n      throws NameNotFoundException, ExecutionException, InterruptedException {\n    for (String packageName : getInstalledPackages()) {\n      int iconResourceId = getResourceId(packageName);\n\n      Uri uri =\n          new Uri.Builder()\n              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n              .authority(packageName)\n              .path(String.valueOf(iconResourceId))\n              .build();\n\n      Bitmap bitmap = Glide.with(context).asBitmap().load(uri).submit().get();\n      assertThat(bitmap).isNotNull();\n    }\n  }\n\n  @Test\n  public void load_withApplicationIconResourceIdUri_asBitmap_withTransformation_nonNullBitmap()\n      throws ExecutionException, InterruptedException {\n    for (String packageName : getInstalledPackages()) {\n      int iconResourceId = getResourceId(packageName);\n\n      Uri uri =\n          new Uri.Builder()\n              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n              .authority(packageName)\n              .path(String.valueOf(iconResourceId))\n              .build();\n\n      Bitmap bitmap =\n          Glide.with(context).asBitmap().apply(centerCropTransform()).load(uri).submit().get();\n      assertThat(bitmap).isNotNull();\n    }\n  }\n\n  @Test\n  public void load_withApplicationIconResourceNameUri_asDrawable_producesNonNullDrawable()\n      throws ExecutionException, InterruptedException, NameNotFoundException {\n    for (String packageName : getInstalledPackages()) {\n      int iconResourceId = getResourceId(packageName);\n\n      Context toUse = context.createPackageContext(packageName, /* flags= */ 0);\n      Resources resources = toUse.getResources();\n      Uri uri =\n          new Uri.Builder()\n              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n              .authority(packageName)\n              .appendPath(resources.getResourceTypeName(iconResourceId))\n              .appendPath(resources.getResourceEntryName(iconResourceId))\n              .build();\n\n      Drawable drawable = Glide.with(context).load(uri).submit().get();\n      assertThat(drawable).isNotNull();\n    }\n  }\n\n  @Test\n  public void load_withApplicationIconResourceNameUri_asDrawable_withTransform_nonNullDrawable()\n      throws ExecutionException, InterruptedException, NameNotFoundException {\n    for (String packageName : getInstalledPackages()) {\n      int iconResourceId = getResourceId(packageName);\n\n      Context toUse = context.createPackageContext(packageName, /* flags= */ 0);\n      Resources resources = toUse.getResources();\n      Uri uri =\n          new Uri.Builder()\n              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n              .authority(packageName)\n              .appendPath(resources.getResourceTypeName(iconResourceId))\n              .appendPath(resources.getResourceEntryName(iconResourceId))\n              .build();\n\n      Drawable drawable = Glide.with(context).load(uri).apply(centerCropTransform()).submit().get();\n      assertThat(drawable).isNotNull();\n    }\n  }\n\n  @Test\n  public void load_withApplicationIconResourceNameUri_asBitmap_producesNonNullBitmap()\n      throws ExecutionException, InterruptedException, NameNotFoundException {\n    for (String packageName : getInstalledPackages()) {\n      int iconResourceId = getResourceId(packageName);\n\n      Context toUse = context.createPackageContext(packageName, /* flags= */ 0);\n      Resources resources = toUse.getResources();\n      Uri uri =\n          new Uri.Builder()\n              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n              .authority(packageName)\n              .appendPath(resources.getResourceTypeName(iconResourceId))\n              .appendPath(resources.getResourceEntryName(iconResourceId))\n              .build();\n\n      Bitmap bitmap = Glide.with(context).asBitmap().load(uri).submit().get();\n      assertThat(bitmap).isNotNull();\n    }\n  }\n\n  @Test\n  public void load_withApplicationIconResourceNameUri_asBitmap_withTransform_nonNullBitmap()\n      throws ExecutionException, InterruptedException, NameNotFoundException {\n    for (String packageName : getInstalledPackages()) {\n      int iconResourceId = getResourceId(packageName);\n\n      Context toUse = context.createPackageContext(packageName, /* flags= */ 0);\n      Resources resources = toUse.getResources();\n      Uri uri =\n          new Uri.Builder()\n              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)\n              .authority(packageName)\n              .appendPath(resources.getResourceTypeName(iconResourceId))\n              .appendPath(resources.getResourceEntryName(iconResourceId))\n              .build();\n\n      Bitmap bitmap =\n          Glide.with(context).asBitmap().apply(centerCropTransform()).load(uri).submit().get();\n      assertThat(bitmap).isNotNull();\n    }\n  }\n\n  private Set<String> getInstalledPackages() {\n    Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);\n    mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);\n    PackageManager packageManager = context.getPackageManager();\n    List<ResolveInfo> pkgAppsList =\n        packageManager.queryIntentActivities(mainIntent, /* flags= */ 0);\n    Set<String> result = new HashSet<>();\n    for (ResolveInfo info : pkgAppsList) {\n      String packageName = info.activityInfo.packageName;\n      int iconResourceId = getResourceId(packageName);\n      if (iconResourceId != 0\n          && doesApplicationPackageNameMatchResourcePackageName(packageName, iconResourceId)) {\n        result.add(info.activityInfo.packageName);\n      }\n    }\n    return result;\n  }\n\n  private int getResourceId(String packageName) {\n    PackageInfo packageInfo;\n    try {\n      packageInfo = context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);\n    } catch (NameNotFoundException e) {\n      return 0;\n    }\n    return packageInfo.applicationInfo.icon;\n  }\n\n  /**\n   * Returns {@code true} iff the resource package name is exactly the same as the containing\n   * application package name for a given resource id.\n   *\n   * <p>The resource package name is the value returned by {@link\n   * Resources#getResourcePackageName(int)}. The application package name is package name of the\n   * enclosing application. If these two things are equal, then we can both construct a Context for\n   * that package and retrieve a resource id for that package from a \"standard\" resource Uri\n   * containing a name instead of an id. If they aren't equal, then we can do only one of the two\n   * required tasks, so our Uri load will always fail. To handle this properly, we'd need callers to\n   * include both package names in the Uri. I'm not aware of any standardized Uri format for doing\n   * so, so these requests will just be treated as unsupported for the time being.\n   *\n   * <p>Take Calendar (emulators API 24 and below) as an example:\n   *\n   * <ul>\n   *   <li>package name: com.google.android.calendar\n   *   <li>resource package name: com.android.calendar\n   * </ul>\n   *\n   * We can construct one of two possible Uris:\n   *\n   * <ul>\n   *   <li>android.resource://com.google.android.calendar/mipmap/ic_icon_calendar.\n   *   <li>android.resource://com.android.calendar/mipmap/ic_icon_calendar.<\n   * </ul>\n   *\n   * From the first Uri, we can obtain the correct Context/Resources for the calendar package, but\n   * our attempts to resolve the correct resource id will fail because we do not have the resource\n   * package name. From the second Uri we cannot obtain the Context/Resources for the calendar\n   * package because the resource package name doesn't match the application package name.\n   */\n  private boolean doesApplicationPackageNameMatchResourcePackageName(\n      String applicationPackageName, int iconResourceId) {\n    try {\n      Context current = context.createPackageContext(applicationPackageName, /* flags= */ 0);\n      String resourcePackageName = current.getResources().getResourcePackageName(iconResourceId);\n      return applicationPackageName.equals(resourcePackageName);\n    } catch (NameNotFoundException e) {\n      // This should never happen\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/PausedRequestsTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.GlideRequests;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport org.junit.Rule;\nimport org.junit.Test;\n\n/**\n * Tests how {@link com.bumptech.glide.request.Request}s behave when the corresponding {@link\n * RequestManager} is paused.\n */\npublic final class PausedRequestsTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @SuppressWarnings(\"unchecked\")\n  @Test\n  public void load_withPlaceHolderSet_requestsPaused_displaysPlaceholder() {\n    final ImageView imageView = new ImageView(context);\n\n    final GlideRequests requests = GlideApp.with(context);\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            requests.pauseAllRequests();\n          }\n        });\n\n    final ColorDrawable expected = new ColorDrawable(Color.RED);\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            requests.load(ResourceIds.drawable.bitmap_alias).placeholder(expected).into(imageView);\n          }\n        });\n\n    assertThat(imageView.getDrawable()).isEqualTo(expected);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/RequestManagerLifecycleTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.fail;\nimport static org.junit.Assume.assumeTrue;\n\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.lifecycle.Lifecycle.Event;\nimport androidx.lifecycle.Lifecycle.State;\nimport androidx.lifecycle.LifecycleObserver;\nimport androidx.lifecycle.LifecycleOwner;\nimport androidx.lifecycle.OnLifecycleEvent;\nimport androidx.test.core.app.ActivityScenario;\nimport androidx.test.core.app.ActivityScenario.ActivityAction;\nimport androidx.test.ext.junit.rules.ActivityScenarioRule;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.instrumentation.R;\nimport com.bumptech.glide.test.DefaultFragmentActivity;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n// This test avoids using FragmentScenario because it doesn't seem to let us to get into the common\n// created but not yet started state, only either before onCreateView or after onResume.\n@RunWith(AndroidJUnit4.class)\npublic class RequestManagerLifecycleTest {\n  private static final String FRAGMENT_TAG = \"fragment\";\n  private static final String FRAGMENT_SIBLING_TAG = \"fragment_sibling\";\n  private static final String CHILD_FRAGMENT_TAG = \"child\";\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n\n  @Rule\n  public final ActivityScenarioRule<DefaultFragmentActivity> scenarioRule =\n      new ActivityScenarioRule<>(DefaultFragmentActivity.class);\n\n  private ActivityScenario<DefaultFragmentActivity> scenario;\n\n  @Before\n  public void setUp() {\n    scenario = scenarioRule.getScenario();\n  }\n\n  @Test\n  public void get_twice_withSameActivity_returnsSameRequestManager() {\n    scenario.moveToState(State.CREATED);\n    scenario.onActivity(\n        activity -> assertThat(Glide.with(activity)).isEqualTo(Glide.with(activity)));\n  }\n\n  @Test\n  public void get_withActivityBeforeCreate_startsRequestManager() {\n    scenario.moveToState(State.CREATED);\n    scenario.onActivity(activity -> assertThat(Glide.with(activity).isPaused()).isFalse());\n  }\n\n  // See b/262668610\n  @SuppressWarnings(\"OnLifecycleEvent\")\n  @Test\n  public void get_withActivityOnDestroy_QPlus_doesNotCrash() {\n    // Activity#isDestroyed's behavior seems to have changed in Q. On Q+, isDestroyed returns false\n    // during onDestroy, so we have to handle that case explicitly.\n    assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);\n    scenario.moveToState(State.CREATED);\n\n    class GetOnDestroy implements LifecycleObserver {\n      private final FragmentActivity activity;\n\n      GetOnDestroy(FragmentActivity activity) {\n        this.activity = activity;\n      }\n\n      @OnLifecycleEvent(Event.ON_DESTROY)\n      public void onDestroy(@NonNull LifecycleOwner owner) {\n        Glide.with(activity);\n      }\n    }\n    scenario.onActivity(\n        activity -> activity.getLifecycle().addObserver(new GetOnDestroy(activity)));\n    scenario.moveToState(State.DESTROYED);\n  }\n\n  @SuppressWarnings(\"OnLifecycleEvent\")\n  @Test\n  public void get_withActivityOnDestroy_afterJellyBeanAndbeforeQ_doesNotCrash() {\n    // Activity#isDestroyed's behavior seems to have changed in Q. On <Q, isDestroyed returns true\n    // during onDestroy, triggering an assertion in Glide. < Jelly bean, isDestroyed is not\n    // available as a method.\n    assumeTrue(\n        Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN\n            && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q);\n    AtomicReference<Exception> thrownException = new AtomicReference<>();\n    scenario.moveToState(State.CREATED);\n\n    class GetOnDestroy implements LifecycleObserver {\n      private final FragmentActivity activity;\n\n      GetOnDestroy(FragmentActivity activity) {\n        this.activity = activity;\n      }\n\n      @OnLifecycleEvent(Event.ON_DESTROY)\n      public void onDestroy(@NonNull LifecycleOwner owner) {\n        try {\n          Glide.with(activity);\n          fail(\"Failed to throw expected exception\");\n        } catch (Exception e) {\n          thrownException.set(e);\n        }\n      }\n    }\n    scenario.onActivity(\n        activity -> activity.getLifecycle().addObserver(new GetOnDestroy(activity)));\n    scenario.moveToState(State.DESTROYED);\n\n    assertThat(thrownException.get())\n        .hasMessageThat()\n        .contains(\"You cannot start a load for a destroyed activity\");\n  }\n\n  @Test\n  public void get_withFragment_beforeFragmentIsAdded_throws() {\n    Fragment fragment = new Fragment();\n    assertThrows(NullPointerException.class, () -> Glide.with(fragment));\n  }\n\n  @Test\n  public void get_withFragment_whenFragmentIsAddedAndVisible_beforeStart_startsRequestManager() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n\n          assertThat(fragment.isVisible()).isTrue();\n          assertThat(Glide.with(fragment).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void requestManager_afterFragmentIsStopped_isPaused() {\n    // Avoid using FragmentScenario because it doesn't seem to let us to get into the common created\n    // but not yet started state, only either before onCreateView or after onResume.\n    final Fragment fragment = new EmptyContainerFragment();\n    scenario.moveToState(State.RESUMED);\n    scenario.onActivity(\n        activity -> {\n          activity\n              .getSupportFragmentManager()\n              .beginTransaction()\n              .add(R.id.container, fragment)\n              .commitNowAllowingStateLoss();\n          // If we call with() for the first time after the fragment is paused but while it's still\n          // visible, then we'll default the request manager to started. So we call with() once here\n          // to make sure the request manager is created before the stop event below.\n          Glide.with(fragment);\n        });\n\n    scenario.moveToState(State.CREATED);\n    scenario.onActivity(\n        activity -> {\n          assertThat(fragment.isVisible()).isTrue();\n          assertThat(Glide.with(fragment).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void get_twice_withSameFragment_returnsSameRequestManager() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          assertThat(Glide.with(fragment)).isEqualTo(Glide.with(fragment));\n        });\n  }\n\n  @Test\n  public void pauseRequestsRecursive_onActivity_pausesFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n\n          Glide.with(activity).pauseAllRequestsRecursive();\n          assertThat(Glide.with(fragment).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void resumeRequestsRecursive_onActivity_resumesFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n\n          Glide.with(activity).pauseAllRequestsRecursive();\n          Glide.with(activity).resumeRequestsRecursive();\n\n          assertThat(Glide.with(fragment).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void pauseRequestsRecursive_onActivity_pausesChildOfChildFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment childFragment = getChildFragment(activity);\n\n          Glide.with(activity).pauseAllRequestsRecursive();\n\n          assertThat(Glide.with(childFragment).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void resumeRequestsRecursive_onActivity_resumesChildOfChildFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment childFragment = getChildFragment(activity);\n\n          Glide.with(activity).pauseAllRequestsRecursive();\n          Glide.with(activity).resumeRequestsRecursive();\n\n          assertThat(Glide.with(childFragment).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void pauseRequestsRecursive_onChildFragmentOfActivity_doesNotPauseActivity() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n\n          Glide.with(fragment).pauseAllRequestsRecursive();\n\n          assertThat(Glide.with(fragment).isPaused()).isTrue();\n          assertThat(Glide.with(activity).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void pauseRequestsRecursive_onChildFragmentOfActivity_pausesChildOfChildFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment parentFragment = getFragment(activity);\n          Fragment childFragment = getChildFragment(activity);\n\n          Glide.with(parentFragment).pauseAllRequestsRecursive();\n\n          assertThat(Glide.with(childFragment).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void resumeRequestsRecursive_onChildFragmentOfActivity_resumesChildOfChildFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment parentFragment = getFragment(activity);\n          Fragment childFragment = getChildFragment(activity);\n\n          Glide.with(parentFragment).pauseAllRequestsRecursive();\n          Glide.with(parentFragment).resumeRequestsRecursive();\n\n          assertThat(Glide.with(childFragment).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void pauseRequests_onActivity_pausesRequestManager() {\n    scenario.moveToState(State.RESUMED);\n    scenario.onActivity(\n        activity -> {\n          Glide.with(activity).pauseAllRequests();\n          assertThat(Glide.with(activity).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void resumeRequests_onActivity_pausesRequestManager() {\n    scenario.moveToState(State.RESUMED);\n    scenario.onActivity(\n        activity -> {\n          Glide.with(activity).pauseAllRequests();\n          Glide.with(activity).resumeRequests();\n          assertThat(Glide.with(activity).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void pauseRequests_onActivity_doesNotPauseChildren() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          initRequestManagers(activity, fragment);\n\n          Glide.with(activity).pauseAllRequests();\n          assertThat(Glide.with(fragment).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void resumeRequests_onActivity_doesNotResumeChildren() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          initRequestManagers(activity, fragment);\n\n          Glide.with(activity).pauseAllRequests();\n          Glide.with(fragment).pauseAllRequests();\n          Glide.with(activity).resumeRequests();\n\n          assertThat(Glide.with(fragment).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void pauseRequests_onFragment_pausesRequestManager() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          Glide.with(fragment).pauseAllRequests();\n          assertThat(Glide.with(fragment).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void resumeRequests_onFragment_resumesRequestManager() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          Glide.with(fragment).pauseAllRequests();\n          Glide.with(fragment).resumeRequests();\n          assertThat(Glide.with(fragment).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void pauseRequests_onChildFragment_doesNotPauseParentFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Glide.with(getChildFragment(activity)).pauseAllRequests();\n\n          assertThat(Glide.with(getFragment(activity)).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void resumeRequests_onChildFragment_doesNotResumeParentFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment parentFragment = getFragment(activity);\n          Fragment childFragment = getChildFragment(activity);\n          Glide.with(childFragment).pauseAllRequests();\n          Glide.with(parentFragment).pauseAllRequests();\n          Glide.with(childFragment).resumeRequests();\n\n          assertThat(Glide.with(parentFragment).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void pauseRequests_onChildFragment_pausesChildFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment childFragment = getChildFragment(activity);\n          Glide.with(childFragment).pauseAllRequests();\n\n          assertThat(Glide.with(childFragment).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void resumeRequests_onChildFragment_resumesChildFragment() {\n    withActivityFragmentAndChildFragment(\n        activity -> {\n          Fragment childFragment = getChildFragment(activity);\n          Glide.with(childFragment).pauseAllRequests();\n          Glide.with(childFragment).resumeRequests();\n\n          assertThat(Glide.with(childFragment).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void pauseRequestsRecursive_onActivity_withTwoSiblingFragments_pausesBothSiblings() {\n    withActivityAndTwoFragmentSiblings(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          Fragment sibling = getSiblingFragment(activity);\n\n          Glide.with(activity).pauseAllRequestsRecursive();\n\n          assertThat(Glide.with(fragment).isPaused()).isTrue();\n          assertThat(Glide.with(sibling).isPaused()).isTrue();\n        });\n  }\n\n  @Test\n  public void resumeRequestsRecursive_onActivity_withTwoSiblingFragments_resumesBothSiblings() {\n    withActivityAndTwoFragmentSiblings(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          Fragment sibling = getSiblingFragment(activity);\n\n          Glide.with(activity).pauseAllRequestsRecursive();\n          Glide.with(activity).resumeRequestsRecursive();\n\n          assertThat(Glide.with(fragment).isPaused()).isFalse();\n          assertThat(Glide.with(sibling).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void pauseRequestsRecursive_onFragment_withSibling_doesNotPauseSibling() {\n    withActivityAndTwoFragmentSiblings(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          Fragment sibling = getSiblingFragment(activity);\n\n          Glide.with(fragment).pauseAllRequestsRecursive();\n\n          assertThat(Glide.with(sibling).isPaused()).isFalse();\n        });\n  }\n\n  @Test\n  public void resumeRequestsRecursive_onFragment_withSibling_doesNotResumeSibling() {\n    withActivityAndTwoFragmentSiblings(\n        activity -> {\n          Fragment fragment = getFragment(activity);\n          Fragment sibling = getSiblingFragment(activity);\n\n          Glide.with(fragment).pauseAllRequestsRecursive();\n          Glide.with(sibling).pauseAllRequests();\n          Glide.with(fragment).resumeRequestsRecursive();\n\n          assertThat(Glide.with(sibling).isPaused()).isTrue();\n        });\n  }\n\n  // We need to create the RequestManager first, or else it will start in the paused state.\n  // TODO(judds): If the parent is explicitly paused, any children added after it's paused should\n  //  probably default to paused when it's created?\n  private void initRequestManagers(FragmentActivity activity, Fragment... fragments) {\n    Glide.with(activity);\n    for (Fragment fragment : fragments) {\n      Glide.with(fragment);\n    }\n  }\n\n  /** Creates the tree: Activity - Fragment - Fragment */\n  private void withActivityAndTwoFragmentSiblings(\n      ActivityAction<DefaultFragmentActivity> assertion) {\n    setupAndRunActivityAction(\n        activity -> {\n          Fragment parentFragment = createAndAddFragment(activity, FRAGMENT_TAG);\n          Fragment siblingFragment = createAndAddFragment(activity, FRAGMENT_SIBLING_TAG);\n          initRequestManagers(activity, parentFragment, siblingFragment);\n        },\n        assertion);\n  }\n\n  /** Creates the tree: Activity - Fragment - Child Fragment */\n  private void withActivityFragmentAndChildFragment(\n      ActivityAction<DefaultFragmentActivity> assertion) {\n    setupAndRunActivityAction(\n        activity -> {\n          Fragment parentFragment = createAndAddFragment(activity, FRAGMENT_TAG);\n          Fragment childFragment = createAndAddFragment(parentFragment, CHILD_FRAGMENT_TAG);\n          initRequestManagers(activity, parentFragment, childFragment);\n        },\n        assertion);\n  }\n\n  private void setupAndRunActivityAction(\n      ActivityAction<DefaultFragmentActivity> setup,\n      ActivityAction<DefaultFragmentActivity> assertion) {\n    scenario.moveToState(State.RESUMED);\n    // Using one onActivity call to do the test setup and another to assert gives the framework\n    // and Glide's fragment management code (onAttach in particular) the opportunity to run before\n    // our\n    // assertions take place.\n    scenario.onActivity(setup);\n    scenario.onActivity(assertion);\n  }\n\n  private Fragment getFragment(FragmentActivity activity) {\n    return getFragment(activity, FRAGMENT_TAG);\n  }\n\n  private Fragment getSiblingFragment(FragmentActivity activity) {\n    return getFragment(activity, FRAGMENT_SIBLING_TAG);\n  }\n\n  private Fragment getChildFragment(FragmentActivity activity) {\n    return getFragment(getFragment(activity).getChildFragmentManager(), CHILD_FRAGMENT_TAG);\n  }\n\n  private Fragment getFragment(FragmentActivity activity, String tag) {\n    return getFragment(activity.getSupportFragmentManager(), tag);\n  }\n\n  private Fragment getFragment(FragmentManager manager, String tag) {\n    return manager.findFragmentByTag(tag);\n  }\n\n  private Fragment createAndAddFragment(FragmentActivity parent, String tag) {\n    return createAndAddFragment(parent.getSupportFragmentManager(), tag);\n  }\n\n  private Fragment createAndAddFragment(Fragment fragment, String tag) {\n    return createAndAddFragment(fragment.getChildFragmentManager(), tag);\n  }\n\n  private Fragment createAndAddFragment(FragmentManager manager, String tag) {\n    Fragment result = new EmptyContainerFragment();\n    manager.beginTransaction().add(R.id.container, result, tag).commitNowAllowingStateLoss();\n    return result;\n  }\n\n  public static final class EmptyContainerFragment extends Fragment {\n    @Override\n    public View onCreateView(\n        @NonNull LayoutInflater inflater,\n        @Nullable ViewGroup container,\n        @Nullable Bundle savedInstanceState) {\n      return inflater.inflate(\n          R.layout.default_fragment_activity, container, /* attachToRoot= */ false);\n    }\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/RequestManagerTest.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.manager.Lifecycle;\nimport com.bumptech.glide.manager.LifecycleListener;\nimport com.bumptech.glide.manager.RequestManagerTreeNode;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.test.ResourceIds.raw;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(AndroidJUnit4.class)\npublic class RequestManagerTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  @Mock private RequestManagerTreeNode treeNode;\n\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private RequestManager requestManager;\n  private Context context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n    Glide glide = Glide.get(context);\n    requestManager =\n        new RequestManager(\n            glide,\n            new Lifecycle() {\n              @Override\n              public void addListener(@NonNull LifecycleListener listener) {\n                listener.onStart();\n              }\n\n              @Override\n              public void removeListener(@NonNull LifecycleListener listener) {\n                // Do nothing.\n              }\n            },\n            treeNode,\n            context);\n  }\n\n  /** Tests #2262. */\n  @Test\n  public void clear_withNonOwningRequestManager_afterOwningManagerIsDestroyed_doesNotThrow() {\n    // First destroy our Fragment/Activity RequestManager.\n    requestManager.onDestroy();\n\n    final ImageView imageView = new ImageView(context);\n    imageView.measure(100, 100);\n    imageView.layout(0, 0, 100, 100);\n    // Then start a new load with our now destroyed RequestManager.\n    concurrency.loadOnMainThread(requestManager.load(ResourceIds.raw.canonical), imageView);\n\n    // Finally clear our new load with any RequestManager other than the one we used to start it.\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.with(context).clear(imageView);\n          }\n        });\n  }\n\n  /** Tests b/69361054. */\n  @Test\n  public void clear_withNonOwningRequestManager_onBackgroundThread_doesNotThrow() {\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            requestManager.onDestroy();\n          }\n        });\n\n    final Target<Drawable> target = concurrency.wait(requestManager.load(raw.canonical).submit());\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            Glide.with(context).clear(target);\n          }\n        });\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/RequestTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.bumptech.glide.testutil.WaitModelLoader;\nimport com.bumptech.glide.testutil.WaitModelLoader.WaitModel;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n/** Tests the behaviors of Requests of all types. */\n@RunWith(AndroidJUnit4.class)\npublic class RequestTest {\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n  @Mock private RequestListener<Drawable> requestListener;\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private Context context;\n  private ImageView imageView;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n    imageView = new ImageView(context);\n    imageView.measure(100, 100);\n    imageView.layout(0, 0, 100, 100);\n\n    // Some emulators only have a single resize thread, so waiting on a latch will block them\n    // forever.\n    Glide.init(\n        context, new GlideBuilder().setSourceExecutor(GlideExecutor.newUnlimitedSourceExecutor()));\n  }\n\n  @Test\n  public void clear_withSingleRequest_nullsOutDrawableInView() {\n    concurrency.loadOnMainThread(GlideApp.with(context).load(ResourceIds.raw.canonical), imageView);\n    assertThat(imageView.getDrawable()).isNotNull();\n\n    concurrency.clearOnMainThread(imageView);\n    assertThat(imageView.getDrawable()).isNull();\n  }\n\n  @Test\n  public void clear_withRequestWithThumbnail_nullsOutDrawableInView() {\n    concurrency.loadOnMainThread(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .thumbnail(GlideApp.with(context).load(ResourceIds.raw.canonical).override(100, 100)),\n        imageView);\n    assertThat(imageView.getDrawable()).isNotNull();\n\n    concurrency.clearOnMainThread(imageView);\n    assertThat(imageView.getDrawable()).isNull();\n  }\n\n  @Test\n  public void onStop_withSingleRequest_doesNotNullOutDrawableInView() {\n    concurrency.loadOnMainThread(GlideApp.with(context).load(ResourceIds.raw.canonical), imageView);\n    assertThat(imageView.getDrawable()).isNotNull();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context).onStop();\n          }\n        });\n    assertThat(imageView.getDrawable()).isNotNull();\n  }\n\n  @Test\n  public void onStop_withRequestWithThumbnail_doesNotNullOutDrawableInView() {\n    concurrency.loadOnMainThread(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .thumbnail(GlideApp.with(context).load(ResourceIds.raw.canonical).override(100, 100)),\n        imageView);\n    assertThat(imageView.getDrawable()).isNotNull();\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context).onStop();\n          }\n        });\n    assertThat(imageView.getDrawable()).isNotNull();\n  }\n\n  @Test\n  public void onStop_withSingleRequestInProgress_nullsOutDrawableInView() {\n    final WaitModel<Integer> model = WaitModelLoader.waitOn(ResourceIds.raw.canonical);\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context).load(ResourceIds.raw.canonical).into(imageView);\n          }\n        });\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context).onStop();\n          }\n        });\n    assertThat(imageView.getDrawable()).isNull();\n    model.countDown();\n  }\n\n  @Test\n  public void onStop_withRequestWithThumbnailBothInProgress_nullsOutDrawableInView() {\n    final WaitModel<Integer> model = WaitModelLoader.waitOn(ResourceIds.raw.canonical);\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context)\n                .load(model)\n                .thumbnail(GlideApp.with(context).load(model).override(100, 100))\n                .into(imageView);\n          }\n        });\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context).onStop();\n          }\n        });\n    assertThat(imageView.getDrawable()).isNull();\n    model.countDown();\n  }\n\n  /** Tests #2555. */\n  @Test\n  public void clear_withRequestWithOnlyFullInProgress_nullsOutDrawableInView() {\n    final WaitModel<Integer> mainModel = WaitModelLoader.waitOn(ResourceIds.raw.canonical);\n    concurrency.loadUntilFirstFinish(\n        GlideApp.with(context)\n            .load(mainModel)\n            .listener(requestListener)\n            .thumbnail(\n                GlideApp.with(context)\n                    .load(ResourceIds.raw.canonical)\n                    .listener(requestListener)\n                    .override(100, 100)),\n        imageView);\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context).clear(imageView);\n          }\n        });\n\n    verify(requestListener, never())\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.DATA_DISK_CACHE),\n            anyBoolean());\n    verify(requestListener, never())\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.RESOURCE_DISK_CACHE),\n            anyBoolean());\n    assertThat(imageView.getDrawable()).isNull();\n    mainModel.countDown();\n  }\n\n  @Test\n  public void clear_withRequestWithOnlyFullInProgress_doesNotNullOutDrawableInView() {\n    final WaitModel<Integer> mainModel = WaitModelLoader.waitOn(ResourceIds.raw.canonical);\n    concurrency.loadUntilFirstFinish(\n        GlideApp.with(context)\n            .load(mainModel)\n            .listener(requestListener)\n            .thumbnail(\n                GlideApp.with(context)\n                    .load(ResourceIds.raw.canonical)\n                    .listener(requestListener)\n                    .override(100, 100)),\n        imageView);\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context).onStop();\n          }\n        });\n\n    verify(requestListener, never())\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.DATA_DISK_CACHE),\n            anyBoolean());\n    verify(requestListener, never())\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.RESOURCE_DISK_CACHE),\n            anyBoolean());\n    assertThat(imageView.getDrawable()).isNotNull();\n    mainModel.countDown();\n  }\n\n  @Test\n  public void onStop_withRequestWithOnlyThumbnailInProgress_doesNotNullOutDrawableInView() {\n    final WaitModel<Integer> thumbModel = WaitModelLoader.waitOn(ResourceIds.raw.canonical);\n    concurrency.loadUntilFirstFinish(\n        GlideApp.with(context)\n            .load(ResourceIds.raw.canonical)\n            .listener(requestListener)\n            .thumbnail(\n                GlideApp.with(context)\n                    .load(thumbModel)\n                    .listener(requestListener)\n                    .override(100, 100)),\n        imageView);\n\n    concurrency.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            GlideApp.with(context).onStop();\n          }\n        });\n\n    verify(requestListener, never())\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.DATA_DISK_CACHE),\n            anyBoolean());\n    verify(requestListener, never())\n        .onResourceReady(\n            ArgumentMatchers.<Drawable>any(),\n            any(),\n            ArgumentMatchers.<Target<Drawable>>any(),\n            eq(DataSource.RESOURCE_DISK_CACHE),\n            anyBoolean());\n\n    // Only requests that are running are paused in onStop. The full request should take priority\n    // over the thumbnail request. Therefore, if the full request is finished in onStop, it should\n    // not be cleared, even if the thumbnail request is still running.\n    assertThat(imageView.getDrawable()).isNotNull();\n    thumbModel.countDown();\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/RoundedCornersRegressionTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners;\nimport com.bumptech.glide.test.BitmapRegressionTester;\nimport com.bumptech.glide.test.CanonicalBitmap;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.RegressionTest;\nimport com.bumptech.glide.test.SplitByCpu;\nimport com.bumptech.glide.test.SplitBySdk;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestName;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.RunWith;\n\n/**\n * Compares the output of RoundedCorners with canonical resource files for all SDKs Glide supports\n * and fails on deltas.\n */\n@RunWith(AndroidJUnit4.class)\n@SplitByCpu\n@SplitBySdk({26, 24, 23, 21, 19, 18, 16})\n@RegressionTest\npublic class RoundedCornersRegressionTest {\n  @Rule public final TestRule tearDownGlide = new TearDownGlide();\n  @Rule public final TestName testName = new TestName();\n\n  private Context context;\n  private BitmapRegressionTester bitmapRegressionTester;\n  private CanonicalBitmap canonicalBitmap;\n\n  @Before\n  public void setUp() throws Exception {\n    context = ApplicationProvider.getApplicationContext();\n    bitmapRegressionTester =\n        BitmapRegressionTester.newInstance(getClass(), testName).assumeShouldRun();\n    canonicalBitmap = new CanonicalBitmap();\n  }\n\n  @Test\n  public void testRoundedCorners() throws ExecutionException, InterruptedException {\n    bitmapRegressionTester.test(\n        GlideApp.with(context)\n            .asBitmap()\n            .load(canonicalBitmap.getBitmap())\n            .transform(new RoundedCorners(5)));\n  }\n\n  @Test\n  public void testRoundedCorners_usePool() throws ExecutionException, InterruptedException {\n    canonicalBitmap = canonicalBitmap.scale(0.1f);\n\n    Bitmap redRect =\n        createRect(\n            Color.RED,\n            canonicalBitmap.getWidth(),\n            canonicalBitmap.getHeight(),\n            Bitmap.Config.ARGB_8888);\n\n    Glide.get(context).getBitmapPool().put(redRect);\n\n    Bitmap roundedRect =\n        bitmapRegressionTester.test(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(canonicalBitmap.getBitmap())\n                .override(canonicalBitmap.getWidth(), canonicalBitmap.getHeight())\n                .transform(new RoundedCorners(5)));\n\n    assertThat(roundedRect).isEqualTo(redRect);\n  }\n\n  @Test\n  public void testRoundedCorners_overRounded() throws ExecutionException, InterruptedException {\n    bitmapRegressionTester.test(\n        GlideApp.with(context)\n            .asBitmap()\n            .load(canonicalBitmap.getBitmap())\n            .transform(new RoundedCorners(20)));\n  }\n\n  private Bitmap createRect(int color, int width, int height, Bitmap.Config config) {\n    final Bitmap result = Bitmap.createBitmap(width, height, config);\n    Canvas canvas = new Canvas(result);\n    canvas.drawColor(color);\n    return result;\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/WideGamutTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assume.assumeTrue;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.CompressFormat;\nimport android.graphics.Bitmap.Config;\nimport android.graphics.ColorSpace;\nimport android.graphics.ColorSpace.Named;\nimport android.os.Build;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;\nimport com.bumptech.glide.load.resource.bitmap.Downsampler;\nimport com.bumptech.glide.load.resource.bitmap.RoundedCorners;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport java.io.ByteArrayOutputStream;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class WideGamutTest {\n  @Rule public final TestRule tearDownGlide = new TearDownGlide();\n  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Before\n  public void setUp() {\n    assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);\n  }\n\n  @Test\n  public void load_withWideGamutImage_returnsWideGamutBitmap() {\n    Bitmap bitmap =\n        concurrency.get(\n            Glide.with(context).asBitmap().load(ResourceIds.raw.webkit_logo_p3).submit());\n    assertThat(bitmap.getConfig()).isEqualTo(Bitmap.Config.RGBA_F16);\n  }\n\n  @Test\n  public void load_withWideGamutImage_bitmapInPoolWithSizeAndConfig_usesBitmapFromPool() {\n    int bitmapDimension = 1000;\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setBitmapPool(new LruBitmapPool(bitmapDimension * bitmapDimension * 8 * 4)));\n    Bitmap expected = Bitmap.createBitmap(bitmapDimension, bitmapDimension, Bitmap.Config.RGBA_F16);\n\n    Glide.get(context).getBitmapPool().put(expected);\n\n    Bitmap bitmap =\n        concurrency.get(\n            Glide.with(context).asBitmap().load(ResourceIds.raw.webkit_logo_p3).submit());\n    assertThat(bitmap).isSameInstanceAs(expected);\n  }\n\n  // TODO: Even with hardware allowed, we get a wide F16. Attempting to decode the resource with\n  // preferred config set to hardware fails with:\n  // \"D/skia    (10312): --- Failed to allocate a hardware bitmap\"\n  @Test\n  public void load_withWideGamutImage_hardwareAllowed_returnsDecodedBitmap() {\n    Bitmap bitmap =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(ResourceIds.raw.webkit_logo_p3)\n                .set(Downsampler.ALLOW_HARDWARE_CONFIG, true)\n                .submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void load_withEncodedPngWideGamutImage_decodesWideGamut() {\n    Bitmap toCompress =\n        Bitmap.createBitmap(\n            100, 100, Bitmap.Config.RGBA_F16, /* hasAlpha= */ true, ColorSpace.get(Named.DCI_P3));\n\n    byte[] data = asPng(toCompress);\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(data).submit());\n    assertThat(bitmap.getConfig()).isEqualTo(Bitmap.Config.RGBA_F16);\n  }\n\n  @Test\n  public void load_withEncodedJpegWideGamutImage_decodesArgb8888() {\n    // TODO(b/71430152): Figure out whether or not this is supposed to pass in API 26 and fail in\n    // API 27.\n    assumeTrue(Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1);\n    Bitmap toCompress =\n        Bitmap.createBitmap(\n            100, 100, Bitmap.Config.RGBA_F16, /* hasAlpha= */ true, ColorSpace.get(Named.DCI_P3));\n\n    byte[] data = asJpeg(toCompress);\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(data).submit());\n    assertThat(bitmap.getConfig()).isEqualTo(Bitmap.Config.ARGB_8888);\n  }\n\n  @Test\n  public void load_withEncodedWebpWideGamutImage_decodesArgb8888() {\n    Bitmap toCompress =\n        Bitmap.createBitmap(\n            100, 100, Bitmap.Config.RGBA_F16, /* hasAlpha= */ true, ColorSpace.get(Named.DCI_P3));\n\n    byte[] data = asWebp(toCompress);\n\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(data).submit());\n    assertThat(bitmap.getConfig()).isEqualTo(Bitmap.Config.ARGB_8888);\n  }\n\n  @Test\n  public void load_withSmallerWideGamutInPool_decodesBitmap() {\n    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();\n    Bitmap toPut = Bitmap.createBitmap(300, 298, Config.RGBA_F16);\n    bitmapPool.put(toPut);\n    // Add a second Bitmap to account for the InputStream decode.\n    bitmapPool.put(Bitmap.createBitmap(toPut));\n\n    Bitmap wideGamut = Bitmap.createBitmap(300, 300, Config.RGBA_F16);\n    byte[] data = asPng(wideGamut);\n    Bitmap bitmap = concurrency.get(Glide.with(context).asBitmap().load(data).submit());\n    assertThat(bitmap).isNotNull();\n  }\n\n  @Test\n  public void circleCrop_withWideGamutBitmap_producesWideGamutBitmap() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.RGBA_F16);\n    byte[] data = asPng(bitmap);\n\n    Bitmap result =\n        concurrency.get(GlideApp.with(context).asBitmap().load(data).circleCrop().submit());\n    assertThat(result).isNotNull();\n    assertThat(result.getConfig()).isEqualTo(Config.RGBA_F16);\n  }\n\n  @Test\n  public void roundedCorners_withWideGamutBitmap_producesWideGamutBitmap() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.RGBA_F16);\n    byte[] data = asPng(bitmap);\n\n    Bitmap result =\n        concurrency.get(\n            GlideApp.with(context)\n                .asBitmap()\n                .load(data)\n                .transform(new RoundedCorners(/* roundingRadius= */ 10))\n                .submit());\n    assertThat(result).isNotNull();\n    assertThat(result.getConfig()).isEqualTo(Config.RGBA_F16);\n  }\n\n  @Test\n  public void loadWideGamutImage_withArgb888OfSufficientSizeInPool_usesArgb8888Bitmap() {\n    Bitmap wideGamut = Bitmap.createBitmap(100, 50, Bitmap.Config.RGBA_F16);\n    byte[] data = asPng(wideGamut);\n\n    Bitmap argb8888 = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setBitmapPool(new LruBitmapPool(wideGamut.getAllocationByteCount() * 5)));\n    Glide.get(context).getBitmapPool().put(argb8888);\n\n    Bitmap result = concurrency.get(Glide.with(context).asBitmap().load(data).submit());\n\n    assertThat(result).isSameInstanceAs(argb8888);\n  }\n\n  private static byte[] asJpeg(Bitmap bitmap) {\n    return toByteArray(bitmap, CompressFormat.JPEG);\n  }\n\n  private static byte[] asPng(Bitmap bitmap) {\n    return toByteArray(bitmap, CompressFormat.PNG);\n  }\n\n  private static byte[] asWebp(Bitmap bitmap) {\n    return toByteArray(bitmap, CompressFormat.WEBP);\n  }\n\n  private static byte[] toByteArray(Bitmap bitmap, CompressFormat format) {\n    ByteArrayOutputStream os = new ByteArrayOutputStream();\n    assertThat(bitmap.compress(format, 100, os)).isTrue();\n    return os.toByteArray();\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/load/engine/executor/IdlingGlideRule.java",
    "content": "package com.bumptech.glide.load.engine.executor;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.IdlingRegistry;\nimport androidx.test.espresso.idling.concurrent.IdlingThreadPoolExecutor;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.UnaryOperator;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\n/** Creates idling executors and registers them with espresso's {@link IdlingRegistry}. */\npublic final class IdlingGlideRule implements TestRule {\n\n  private final UnaryOperator<GlideBuilder> additionalOptions;\n\n  public static IdlingGlideRule newGlideRule(UnaryOperator<GlideBuilder> additionalOptions) {\n    return new IdlingGlideRule(additionalOptions);\n  }\n\n  private IdlingGlideRule(UnaryOperator<GlideBuilder> additionalOptions) {\n    this.additionalOptions = additionalOptions;\n  }\n\n  @Override\n  public Statement apply(Statement base, Description description) {\n    return new Statement() {\n      @Override\n      public void evaluate() throws Throwable {\n        IdlingRegistry idlingRegistry = IdlingRegistry.getInstance();\n\n        IdlingThreadPoolExecutor sourceExecutor =\n            newIdlingThreadPoolExecutor(\n                GlideExecutor.DEFAULT_SOURCE_EXECUTOR_NAME,\n                GlideExecutor.calculateBestThreadCount());\n        idlingRegistry.register(sourceExecutor);\n        IdlingThreadPoolExecutor diskCacheExecutor =\n            newIdlingThreadPoolExecutor(\n                GlideExecutor.DEFAULT_DISK_CACHE_EXECUTOR_NAME,\n                /* poolSize= */ GlideExecutor.DEFAULT_DISK_CACHE_EXECUTOR_THREADS);\n        idlingRegistry.register(diskCacheExecutor);\n        IdlingThreadPoolExecutor animationExecutor =\n            newIdlingThreadPoolExecutor(\n                GlideExecutor.DEFAULT_ANIMATION_EXECUTOR_NAME,\n                GlideExecutor.calculateAnimationExecutorThreadCount());\n        idlingRegistry.register(animationExecutor);\n        try {\n          Glide.init(\n              ApplicationProvider.getApplicationContext(),\n              additionalOptions\n                  .apply(new GlideBuilder())\n                  .setSourceExecutor(new GlideExecutor(sourceExecutor))\n                  .setDiskCacheExecutor(new GlideExecutor(diskCacheExecutor))\n                  .setAnimationExecutor(new GlideExecutor(animationExecutor)));\n          base.evaluate();\n        } finally {\n          idlingRegistry.unregister(sourceExecutor);\n          idlingRegistry.unregister(diskCacheExecutor);\n          idlingRegistry.unregister(animationExecutor);\n          Glide.tearDown();\n        }\n      }\n    };\n  }\n\n  private static IdlingThreadPoolExecutor newIdlingThreadPoolExecutor(String name, int poolSize) {\n    return new IdlingThreadPoolExecutor(\n        name,\n        /* corePoolSize= */ poolSize,\n        /* maximumPoolSize= */ poolSize,\n        /* keepAliveTime= */ 1,\n        TimeUnit.SECONDS,\n        new LinkedBlockingQueue<>(),\n        Thread::new);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/DownsamplerEmulatorTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static android.graphics.Bitmap.CompressFormat.JPEG;\nimport static android.graphics.Bitmap.CompressFormat.PNG;\nimport static android.graphics.Bitmap.CompressFormat.WEBP;\nimport static android.os.Build.VERSION_CODES.KITKAT;\nimport static com.bumptech.glide.load.resource.bitmap.DownsamplerEmulatorTest.Api.apis;\nimport static com.bumptech.glide.load.resource.bitmap.DownsamplerEmulatorTest.Api.atAndAbove;\nimport static com.bumptech.glide.load.resource.bitmap.DownsamplerEmulatorTest.Api.below;\nimport static com.bumptech.glide.load.resource.bitmap.DownsamplerEmulatorTest.Api.onAllApisAndAllFormatsExpect;\nimport static com.bumptech.glide.load.resource.bitmap.DownsamplerEmulatorTest.Formats.Builder.allFormats;\nimport static com.bumptech.glide.load.resource.bitmap.DownsamplerEmulatorTest.Formats.Builder.formats;\nimport static org.junit.Assert.fail;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.CompressFormat;\nimport android.graphics.Bitmap.Config;\nimport android.os.Build;\nimport android.os.Build.VERSION;\nimport android.os.Build.VERSION_CODES;\nimport android.util.DisplayMetrics;\nimport androidx.annotation.Nullable;\nimport androidx.exifinterface.media.ExifInterface;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * Runs tests to make sure that DownsampleStrategy provides the output we expect.\n *\n * <p>WEBP at and above N rounds. Webp below N floors. PNG always floors. JPEG always rounds.\n */\n@RunWith(AndroidJUnit4.class)\n@SuppressWarnings(\"VisibleForTests\")\npublic class DownsamplerEmulatorTest {\n\n  @Test\n  public void calculateScaling_withAtMost() throws IOException {\n    new Tester(DownsampleStrategy.AT_MOST)\n        // See #3673\n        .setTargetDimensions(1977, 2636)\n        .givenImageWithDimensionsOf(3024, 4032, onAllApisAndAllFormatsExpect(1512, 2016))\n        .setTargetDimensions(100, 100)\n        .givenSquareImageWithDimensionOf(100, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(400, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(300, onAllApisAndAllFormatsExpect(75, 75))\n        .givenImageWithDimensionsOf(\n            799,\n            100,\n            atAndAbove(VERSION_CODES.N)\n                .with(formats(JPEG, WEBP).expect(100, 13), formats(PNG).expect(99, 12)),\n            below(VERSION_CODES.N)\n                .with(formats(JPEG).expect(100, 13), formats(PNG, WEBP).expect(99, 12)))\n        .givenImageWithDimensionsOf(\n            800,\n            100,\n            atAndAbove(VERSION_CODES.N)\n                .with(formats(JPEG, WEBP).expect(100, 13), formats(PNG).expect(100, 12)),\n            below(VERSION_CODES.N)\n                .with(formats(JPEG).expect(100, 13), formats(PNG, WEBP).expect(100, 12)))\n        .givenImageWithDimensionsOf(801, 100, onAllApisAndAllFormatsExpect(50, 6))\n        .givenImageWithDimensionsOf(\n            100,\n            800,\n            atAndAbove(VERSION_CODES.N)\n                .with(formats(JPEG, WEBP).expect(13, 100), formats(PNG).expect(12, 100)),\n            below(VERSION_CODES.N)\n                .with(formats(JPEG).expect(13, 100), formats(PNG, WEBP).expect(12, 100)))\n        .givenImageWithDimensionsOf(\n            801,\n            100,\n            below(KITKAT)\n                .with(\n                    // JPEG is correct because CENTER_INSIDE wants to give a subsequent\n                    // transformation an image that is greater in size than the requested size. On\n                    // Api > VERSION_CODES.KITKAT, CENTER_INSIDE can do the transformation itself.\n                    // On < VERSION_CODES.KITKAT, it has to assume a subsequent transformation will\n                    // be called.\n                    formats(JPEG).expect(50, 6), formats(PNG, WEBP).expect(50, 6)))\n        .givenImageWithDimensionsOf(87, 78, onAllApisAndAllFormatsExpect(87, 78))\n        // This set of examples demonstrate that webp uses round on N+ and floor < N.\n        .setTargetDimensions(13, 13)\n        .givenSquareImageWithDimensionOf(\n            99,\n            atAndAbove(KITKAT)\n                .with(\n                    // 99 / 8.0 = 12.375. ceil(12.375) = 13. round(12.375) = 12. floor(12.375) = 12.\n                    formats(JPEG).expect(13, 13), formats(PNG, WEBP).expect(12, 12)),\n            below(KITKAT).with(formats(JPEG).expect(13, 13), formats(PNG, WEBP).expect(12, 12)))\n        .givenSquareImageWithDimensionOf(\n            100,\n            atAndAbove(VERSION_CODES.N)\n                .with(\n                    // 100 / 8.0 = 12.5. ceil(12.5) = 13. round(12.5) = 13. floor(12.5) = 12.\n                    formats(JPEG, WEBP).expect(13, 13), formats(PNG).expect(12, 12)),\n            below(VERSION_CODES.N)\n                .with(formats(JPEG).expect(13, 13), formats(PNG, WEBP).expect(12, 12)))\n        // Upscaling\n        .setTargetDimensions(500, 500)\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(200, 200))\n        .givenSquareImageWithDimensionOf(450, onAllApisAndAllFormatsExpect(450, 450))\n        .givenImageWithDimensionsOf(200, 450, onAllApisAndAllFormatsExpect(200, 450))\n        // Original scaling\n        .setTargetDimensions(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n        .givenImageWithDimensionsOf(1821, 2634, onAllApisAndAllFormatsExpect(1821, 2634))\n        .run();\n  }\n\n  @Test\n  public void calculateScaling_withGainmap_androidU_withAtMost() throws IOException {\n    new Tester(DownsampleStrategy.AT_MOST)\n        // See #3673\n        .setTargetDimensions(1977, 2636)\n        .givenGainmapImageWithDimensionsOf(\n            3024,\n            4032,\n            /* allowHardwareConfig= */ false,\n            atAndAbove(34)\n                .with(new Formats(new CompressFormat[] {CompressFormat.JPEG}, 1512, 2016)))\n        .givenGainmapImageWithDimensionsOf(\n            3024,\n            4032,\n            /* allowHardwareConfig= */ true,\n            atAndAbove(34)\n                .with(new Formats(new CompressFormat[] {CompressFormat.JPEG}, 1512, 2016)))\n        .run();\n  }\n\n  @Test\n  public void calculateScaling_withAtLeast() throws IOException {\n    new Tester(DownsampleStrategy.AT_LEAST)\n        // See #3673\n        .setTargetDimensions(1977, 2636)\n        .givenImageWithDimensionsOf(3024, 4032, onAllApisAndAllFormatsExpect(3024, 4032))\n        .setTargetDimensions(100, 100)\n        .givenSquareImageWithDimensionOf(100, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(300, onAllApisAndAllFormatsExpect(150, 150))\n        .givenImageWithDimensionsOf(799, 100, onAllApisAndAllFormatsExpect(799, 100))\n        .givenImageWithDimensionsOf(800, 100, onAllApisAndAllFormatsExpect(800, 100))\n        .givenImageWithDimensionsOf(801, 100, onAllApisAndAllFormatsExpect(801, 100))\n        .givenImageWithDimensionsOf(100, 800, onAllApisAndAllFormatsExpect(100, 800))\n        .givenImageWithDimensionsOf(87, 78, onAllApisAndAllFormatsExpect(87, 78))\n        // Upscaling\n        .setTargetDimensions(500, 500)\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(200, 200))\n        .givenSquareImageWithDimensionOf(450, onAllApisAndAllFormatsExpect(450, 450))\n        .givenImageWithDimensionsOf(200, 450, onAllApisAndAllFormatsExpect(200, 450))\n        // Original scaling\n        .setTargetDimensions(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n        .givenImageWithDimensionsOf(1821, 2634, onAllApisAndAllFormatsExpect(1821, 2634))\n        .run();\n  }\n\n  @Test\n  public void calculateScaling_withCenterInside() throws IOException {\n    new Tester(DownsampleStrategy.CENTER_INSIDE)\n        // See #3673\n        .setTargetDimensions(1977, 2636)\n        .givenImageWithDimensionsOf(\n            3024,\n            4032,\n            atAndAbove(KITKAT).with(allFormats().expect(1977, 2636)),\n            below(KITKAT).with(allFormats().expect(3024, 4032)))\n        .setTargetDimensions(100, 100)\n        .givenSquareImageWithDimensionOf(100, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(400, onAllApisAndAllFormatsExpect(100, 100))\n        .givenImageWithDimensionsOf(\n            300,\n            300,\n            atAndAbove(KITKAT).with(allFormats().expect(100, 100)),\n            below(KITKAT).with(allFormats().expect(150, 150)))\n        .givenImageWithDimensionsOf(\n            799,\n            100,\n            atAndAbove(KITKAT).with(allFormats().expect(100, 13)),\n            below(KITKAT).with(formats(JPEG).expect(200, 25), formats(PNG, WEBP).expect(199, 25)))\n        .givenImageWithDimensionsOf(\n            800,\n            100,\n            atAndAbove(KITKAT).with(allFormats().expect(100, 13)),\n            below(KITKAT).with(formats(JPEG).expect(100, 13), formats(PNG, WEBP).expect(100, 12)))\n        .givenImageWithDimensionsOf(\n            801,\n            100,\n            atAndAbove(VERSION_CODES.N)\n                .with(formats(JPEG, WEBP).expect(100, 13), formats(PNG).expect(100, 12)),\n            apis(KITKAT, VERSION_CODES.M)\n                .with(formats(JPEG).expect(100, 13), formats(PNG, WEBP).expect(100, 12)),\n            below(KITKAT)\n                .with(\n                    // JPEG is correct because CENTER_INSIDE wants to give a subsequent\n                    // transformation an image that is greater in size than the requested size. On\n                    // Api > VERSION_CODES.KITKAT, CENTER_INSIDE can do the transformation itself.\n                    // On < VERSION_CODES.KITKAT, it has to assume a subsequent transformation will\n                    // be called.\n                    formats(JPEG).expect(101, 13), formats(PNG, WEBP).expect(100, 12)))\n        .givenImageWithDimensionsOf(\n            100,\n            800,\n            atAndAbove(KITKAT).with(allFormats().expect(13, 100)),\n            below(KITKAT).with(formats(JPEG).expect(13, 100), formats(PNG, WEBP).expect(12, 100)))\n        .givenImageWithDimensionsOf(87, 78, onAllApisAndAllFormatsExpect(87, 78))\n        .setTargetDimensions(897, 897)\n        .givenImageWithDimensionsOf(\n            2208,\n            1520,\n            atAndAbove(KITKAT).with(allFormats().expect(897, 618)),\n            below(KITKAT).with(allFormats().expect(1104, 760)))\n        // Upscaling\n        .setTargetDimensions(500, 500)\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(200, 200))\n        .givenSquareImageWithDimensionOf(450, onAllApisAndAllFormatsExpect(450, 450))\n        .givenImageWithDimensionsOf(200, 450, onAllApisAndAllFormatsExpect(200, 450))\n        // Original scaling\n        .setTargetDimensions(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n        .givenImageWithDimensionsOf(1821, 2634, onAllApisAndAllFormatsExpect(1821, 2634))\n        .run();\n  }\n\n  @Test\n  public void calculateScaling_withCenterOutside() throws IOException {\n    new Tester(DownsampleStrategy.CENTER_OUTSIDE)\n        // See #3673\n        .setTargetDimensions(1977, 2636)\n        .givenImageWithDimensionsOf(\n            3024,\n            4032,\n            atAndAbove(KITKAT).with(allFormats().expect(1977, 2636)),\n            below(KITKAT).with(allFormats().expect(3024, 4032)))\n        .setTargetDimensions(100, 100)\n        .givenSquareImageWithDimensionOf(100, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(400, onAllApisAndAllFormatsExpect(100, 100))\n        .givenImageWithDimensionsOf(\n            300,\n            300,\n            atAndAbove(KITKAT).with(allFormats().expect(100, 100)),\n            below(KITKAT).with(allFormats().expect(150, 150)))\n        .givenImageWithDimensionsOf(799, 100, onAllApisAndAllFormatsExpect(799, 100))\n        .givenImageWithDimensionsOf(800, 100, onAllApisAndAllFormatsExpect(800, 100))\n        .givenImageWithDimensionsOf(801, 100, onAllApisAndAllFormatsExpect(801, 100))\n        .givenImageWithDimensionsOf(100, 800, onAllApisAndAllFormatsExpect(100, 800))\n        .givenImageWithDimensionsOf(\n            87,\n            78,\n            atAndAbove(KITKAT).with(allFormats().expect(112, 100)),\n            below(KITKAT).with(allFormats().expect(87, 78)))\n        // Upscaling\n        .setTargetDimensions(500, 500)\n        .givenSquareImageWithDimensionOf(\n            200,\n            atAndAbove(KITKAT).with(allFormats().expect(500, 500)),\n            below(KITKAT).with(allFormats().expect(200, 200)))\n        .givenSquareImageWithDimensionOf(\n            450,\n            atAndAbove(KITKAT).with(allFormats().expect(500, 500)),\n            below(KITKAT).with(allFormats().expect(450, 450)))\n        .givenImageWithDimensionsOf(\n            200,\n            450,\n            atAndAbove(KITKAT).with(allFormats().expect(500, 1125)),\n            below(KITKAT).with(allFormats().expect(200, 450)))\n        // Original scaling\n        .setTargetDimensions(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n        .givenImageWithDimensionsOf(1821, 2634, onAllApisAndAllFormatsExpect(1821, 2634))\n        .run();\n  }\n\n  @Test\n  public void calculateScaling_withNone() throws IOException {\n    new Tester(DownsampleStrategy.NONE)\n        // See #3673\n        .setTargetDimensions(1977, 2636)\n        .givenImageWithDimensionsOf(3024, 4032, onAllApisAndAllFormatsExpect(3024, 4032))\n        .setTargetDimensions(100, 100)\n        .givenSquareImageWithDimensionOf(100, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(200, 200))\n        .givenSquareImageWithDimensionOf(400, onAllApisAndAllFormatsExpect(400, 400))\n        .givenSquareImageWithDimensionOf(300, onAllApisAndAllFormatsExpect(300, 300))\n        .givenImageWithDimensionsOf(799, 100, onAllApisAndAllFormatsExpect(799, 100))\n        .givenImageWithDimensionsOf(800, 100, onAllApisAndAllFormatsExpect(800, 100))\n        .givenImageWithDimensionsOf(801, 100, onAllApisAndAllFormatsExpect(801, 100))\n        .givenImageWithDimensionsOf(100, 800, onAllApisAndAllFormatsExpect(100, 800))\n        .givenImageWithDimensionsOf(87, 78, onAllApisAndAllFormatsExpect(87, 78))\n        .setTargetDimensions(500, 500)\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(200, 200))\n        .givenSquareImageWithDimensionOf(450, onAllApisAndAllFormatsExpect(450, 450))\n        .givenImageWithDimensionsOf(200, 450, onAllApisAndAllFormatsExpect(200, 450))\n        // Original scaling\n        .setTargetDimensions(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n        .givenImageWithDimensionsOf(1821, 2634, onAllApisAndAllFormatsExpect(1821, 2634))\n        .run();\n  }\n\n  @Test\n  public void calculateScaling_withFitCenter() throws IOException {\n    new Tester(DownsampleStrategy.FIT_CENTER)\n        // See #3673\n        .setTargetDimensions(1977, 2636)\n        .givenImageWithDimensionsOf(\n            3024,\n            4032,\n            atAndAbove(KITKAT).with(allFormats().expect(1977, 2636)),\n            below(KITKAT).with(allFormats().expect(3024, 4032)))\n        .setTargetDimensions(100, 100)\n        .givenSquareImageWithDimensionOf(100, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(200, onAllApisAndAllFormatsExpect(100, 100))\n        .givenSquareImageWithDimensionOf(400, onAllApisAndAllFormatsExpect(100, 100))\n        .givenImageWithDimensionsOf(\n            300,\n            300,\n            atAndAbove(KITKAT).with(allFormats().expect(100, 100)),\n            below(KITKAT).with(allFormats().expect(150, 150)))\n        .givenImageWithDimensionsOf(\n            799,\n            100,\n            atAndAbove(KITKAT).with(allFormats().expect(100, 13)),\n            below(KITKAT).with(formats(JPEG).expect(200, 25), formats(PNG, WEBP).expect(199, 25)))\n        .givenImageWithDimensionsOf(\n            800,\n            100,\n            atAndAbove(KITKAT).with(allFormats().expect(100, 13)),\n            below(KITKAT).with(formats(JPEG).expect(100, 13), formats(PNG, WEBP).expect(100, 12)))\n        .givenImageWithDimensionsOf(\n            801,\n            100,\n            atAndAbove(VERSION_CODES.N)\n                .with(formats(JPEG, WEBP).expect(100, 13), formats(PNG).expect(100, 12)),\n            apis(KITKAT, VERSION_CODES.M)\n                .with(formats(JPEG).expect(100, 13), formats(PNG, WEBP).expect(100, 12)),\n            below(KITKAT)\n                .with(\n                    // JPEG is correct because FIT_CENTER wants to give a subsequent transformation\n                    // an image that is greater in size than the requested size. On\n                    // Api > VERSION_CODES.KITKAT, FIT_CENTER can do the transformation itself.\n                    // On < VERSION_CODES.KITKAT, it has to assume a transformation will be run\n                    // after it that will fix the rounding error.\n                    formats(JPEG).expect(101, 13), formats(PNG, WEBP).expect(100, 12)))\n        .givenImageWithDimensionsOf(\n            100,\n            800,\n            atAndAbove(KITKAT).with(allFormats().expect(13, 100)),\n            below(KITKAT).with(formats(JPEG).expect(13, 100), formats(PNG, WEBP).expect(12, 100)))\n        .givenImageWithDimensionsOf(\n            87,\n            78,\n            atAndAbove(KITKAT).with(allFormats().expect(100, 90)),\n            below(KITKAT).with(allFormats().expect(87, 78)))\n        .setTargetDimensions(897, 897)\n        .givenImageWithDimensionsOf(\n            2208,\n            1520,\n            atAndAbove(KITKAT).with(allFormats().expect(897, 618)),\n            below(KITKAT).with(allFormats().expect(1104, 760)))\n        .setTargetDimensions(270, 270)\n        // This set of larger image examples exercises sample sizes > 8. Android' scaling logic\n        // varies for jpegs.\n        .givenImageWithDimensionsOf(\n            9014,\n            1638,\n            // 15 and 16 will OOM so don't run them.\n            atAndAbove(KITKAT).with(allFormats().expect(270, 49)),\n            apis(VERSION_CODES.JELLY_BEAN_MR1, VERSION_CODES.JELLY_BEAN_MR2)\n                .with(allFormats().expect(281, 51)))\n        .givenImageWithDimensionsOf(\n            1638,\n            9014,\n            // 15 and 16 will OOM so don't run them.\n            atAndAbove(KITKAT).with(allFormats().expect(49, 270)),\n            apis(VERSION_CODES.JELLY_BEAN_MR1, VERSION_CODES.JELLY_BEAN_MR2)\n                .with(allFormats().expect(51, 281)))\n        .givenImageWithDimensionsOf(\n            1638,\n            1638,\n            atAndAbove(KITKAT).with(allFormats().expect(270, 270)),\n            below(KITKAT).with(formats(JPEG).expect(410, 410), formats(PNG, WEBP).expect(409, 409)))\n        .givenImageWithDimensionsOf(\n            4507,\n            819,\n            atAndAbove(KITKAT).with(allFormats().expect(270, 49)),\n            below(KITKAT).with(formats(JPEG).expect(282, 51), formats(PNG, WEBP).expect(281, 51)))\n        .givenImageWithDimensionsOf(\n            4503,\n            819,\n            atAndAbove(KITKAT).with(allFormats().expect(270, 49)),\n            below(KITKAT).with(allFormats().expect(281, 51)))\n        // Upscaling\n        .setTargetDimensions(500, 500)\n        .givenSquareImageWithDimensionOf(\n            200,\n            atAndAbove(KITKAT).with(allFormats().expect(500, 500)),\n            below(KITKAT).with(allFormats().expect(200, 200)))\n        .givenSquareImageWithDimensionOf(\n            450,\n            atAndAbove(KITKAT).with(allFormats().expect(500, 500)),\n            below(KITKAT).with(allFormats().expect(450, 450)))\n        .givenImageWithDimensionsOf(\n            200,\n            450,\n            atAndAbove(KITKAT).with(allFormats().expect(222, 500)),\n            below(KITKAT).with(allFormats().expect(200, 450)))\n        // Original scaling\n        .setTargetDimensions(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n        .givenImageWithDimensionsOf(1821, 2634, onAllApisAndAllFormatsExpect(1821, 2634))\n        .run();\n  }\n\n  /** Returns an error string if the test failed, and {@code null} otherwise. */\n  @Nullable\n  private static String runScaleTest(\n      CompressFormat format,\n      int initialWidth,\n      int initialHeight,\n      int targetWidth,\n      int targetHeight,\n      int exifOrientation,\n      boolean hasGainmap,\n      boolean allowHardwareConfig,\n      DownsampleStrategy strategy,\n      int expectedWidth,\n      int expectedHeight)\n      throws IOException {\n    Downsampler downsampler = buildDownsampler();\n\n    InputStream is =\n        openBitmapStream(format, initialWidth, initialHeight, exifOrientation, hasGainmap);\n    Options options = new Options().set(DownsampleStrategy.OPTION, strategy);\n    options.set(Downsampler.ALLOW_HARDWARE_CONFIG, allowHardwareConfig);\n    Bitmap bitmap;\n    try {\n      bitmap = downsampler.decode(is, targetWidth, targetHeight, options).get();\n    } catch (OutOfMemoryError e) {\n      return \"API: \"\n          + Build.VERSION.SDK_INT\n          + \", os: \"\n          + Build.VERSION.RELEASE\n          + \", format: \"\n          + format\n          + \", strategy: \"\n          + strategy\n          + \", orientation: \"\n          + exifOrientation\n          + \", allowHardwareConfig: \"\n          + allowHardwareConfig\n          + \" -\"\n          + \" Initial \"\n          + readableDimens(initialWidth, initialHeight)\n          + \" Target \"\n          + readableDimens(targetWidth, targetHeight)\n          + \" Expected \"\n          + readableDimens(expectedWidth, expectedHeight)\n          + \" but threw OutOfMemoryError\";\n    }\n    try {\n      if (VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE\n          && (bitmap.getWidth() != expectedWidth\n              || bitmap.getHeight() != expectedHeight\n              || bitmap.hasGainmap() != hasGainmap)) {\n        return \"API: \"\n            + Build.VERSION.SDK_INT\n            + \", os: \"\n            + Build.VERSION.RELEASE\n            + \", format: \"\n            + format\n            + \", strategy: \"\n            + strategy\n            + \", orientation: \"\n            + exifOrientation\n            + \", hasGainmap: \"\n            + hasGainmap\n            + \", allowHardwareConfig: \"\n            + allowHardwareConfig\n            + \" -\"\n            + \" Initial \"\n            + readableDimens(initialWidth, initialHeight)\n            + \" Target \"\n            + readableDimens(targetWidth, targetHeight)\n            + \" Expected \"\n            + readableDimensAndHasGainmap(expectedWidth, expectedHeight, hasGainmap)\n            + \", but Received \"\n            + readableDimensAndHasGainmap(\n                bitmap.getWidth(), bitmap.getHeight(), bitmap.hasGainmap());\n      } else if (bitmap.getWidth() != expectedWidth || bitmap.getHeight() != expectedHeight) {\n        return \"API: \"\n            + Build.VERSION.SDK_INT\n            + \", os: \"\n            + Build.VERSION.RELEASE\n            + \", format: \"\n            + format\n            + \", strategy: \"\n            + strategy\n            + \", orientation: \"\n            + exifOrientation\n            + \", allowHardwareConfig: \"\n            + allowHardwareConfig\n            + \" -\"\n            + \" Initial \"\n            + readableDimens(initialWidth, initialHeight)\n            + \" Target \"\n            + readableDimens(targetWidth, targetHeight)\n            + \" Expected \"\n            + readableDimens(expectedWidth, expectedHeight)\n            + \", but Received \"\n            + readableDimens(bitmap.getWidth(), bitmap.getHeight());\n      }\n    } finally {\n      bitmap.recycle();\n    }\n    return null;\n  }\n\n  private static String readableDimens(int width, int height) {\n    return \"[\" + width + \"x\" + height + \"]\";\n  }\n\n  private static String readableDimensAndHasGainmap(int width, int height, boolean hasGainmap) {\n    return \"[\" + width + \"x\" + height + \"], hasGainmap=\" + hasGainmap;\n  }\n\n  private static Downsampler buildDownsampler() {\n    List<ImageHeaderParser> parsers =\n        Collections.<ImageHeaderParser>singletonList(new DefaultImageHeaderParser());\n    DisplayMetrics displayMetrics = new DisplayMetrics();\n    // XHDPI.\n    displayMetrics.densityDpi = 320;\n    BitmapPool bitmapPool = new BitmapPoolAdapter();\n    ArrayPool arrayPool = new LruArrayPool();\n    return new Downsampler(parsers, displayMetrics, bitmapPool, arrayPool);\n  }\n\n  private static InputStream openBitmapStream(\n      CompressFormat format, int width, int height, int exifOrientation, boolean hasGainmap) {\n    Preconditions.checkArgument(\n        format == CompressFormat.JPEG || exifOrientation == ExifInterface.ORIENTATION_UNDEFINED,\n        \"Can only orient JPEGs, but asked for orientation: \"\n            + exifOrientation\n            + \" with format: \"\n            + format);\n\n    // TODO: support orientations for formats other than JPEG.\n    if (format == CompressFormat.JPEG) {\n      return openFileStream(width, height, exifOrientation, hasGainmap);\n    } else {\n      return openInMemoryStream(format, width, height, hasGainmap);\n    }\n  }\n\n  private static InputStream openFileStream(\n      int width, int height, int exifOrientation, boolean hasGainmap) {\n    int rotationDegrees = TransformationUtils.getExifOrientationDegrees(exifOrientation);\n    if (rotationDegrees == 270 || rotationDegrees == 90) {\n      int temp = width;\n      width = height;\n      height = temp;\n    }\n\n    Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);\n    if (hasGainmap && VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) {\n      bitmap.setGainmap(\n          // Intentionally not directly imported due to test failures with class resolution when\n          // running on SDK levels < 34. Also, do not extract methods with Gainmap in the method\n          // signature for the same reason.\n          new android.graphics.Gainmap(Bitmap.createBitmap(width / 2, height / 2, Config.ALPHA_8)));\n    }\n\n    OutputStream os = null;\n    try {\n      File tempFile =\n          File.createTempFile(\n              \"ds-\" + width + \"-\" + height + \"-\" + exifOrientation + \"-\" + hasGainmap,\n              \".jpeg\",\n              ApplicationProvider.getApplicationContext().getCacheDir());\n      os = new BufferedOutputStream(new FileOutputStream(tempFile));\n      bitmap.compress(CompressFormat.JPEG, /* quality= */ 100, os);\n      bitmap.recycle();\n      os.close();\n\n      ExifInterface exifInterface = new ExifInterface(tempFile.getAbsolutePath());\n      exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(exifOrientation));\n      exifInterface.saveAttributes();\n\n      InputStream result = new BufferedInputStream(new FileInputStream(tempFile));\n      if (!tempFile.delete()) {\n        throw new IllegalStateException(\"Failed to delete: \" + tempFile);\n      }\n      return result;\n    } catch (IOException e) {\n      throw new IllegalStateException(e);\n    } finally {\n      if (os != null) {\n        try {\n          os.close();\n        } catch (IOException e) {\n        }\n      }\n    }\n  }\n\n  private static InputStream openInMemoryStream(\n      CompressFormat format, int width, int height, boolean hasGainmap) {\n    Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);\n    if (hasGainmap && VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) {\n      // Intentionally not directly imported due to test failures with class resolution when\n      // running on SDK levels < 34. Also, do not extract methods with Gainmap in the method\n      // signature for the same reason.\n      bitmap.setGainmap(\n          new android.graphics.Gainmap(Bitmap.createBitmap(width / 2, height / 2, Config.ALPHA_8)));\n    }\n    ByteArrayOutputStream os = new ByteArrayOutputStream();\n    bitmap.compress(format, 100 /*quality*/, os);\n    bitmap.recycle();\n    byte[] data = os.toByteArray();\n    return new ByteArrayInputStream(data);\n  }\n\n  static final class Tester {\n    private final DownsampleStrategy strategy;\n    private final List<TestCase> testCases = new ArrayList<>();\n\n    private int targetWidth;\n    private int targetHeight;\n\n    Tester(DownsampleStrategy strategy) {\n      this.strategy = strategy;\n    }\n\n    Tester setTargetDimensions(int targetWidth, int targetHeight) {\n      this.targetWidth = targetWidth;\n      this.targetHeight = targetHeight;\n      return this;\n    }\n\n    Tester givenSquareImageWithDimensionOf(int dimension, Api... apis) {\n      return givenImageWithDimensionsOf(dimension, dimension, apis);\n    }\n\n    Tester givenGainmapImageWithDimensionsOf(\n        int sourceWidth, int sourceHeight, boolean allowHardwareConfig, Api... apis) {\n      testCases.add(\n          new TestCase.Builder()\n              .setSourceWidth(sourceWidth)\n              .setSourceHeight(sourceHeight)\n              .setTargetWidth(targetWidth)\n              .setTargetHeight(targetHeight)\n              .setHasGainmap(true)\n              .setAllowHardwareConfig(allowHardwareConfig)\n              .setApis(apis)\n              .build());\n      return this;\n    }\n\n    Tester givenImageWithDimensionsOf(int sourceWidth, int sourceHeight, Api... apis) {\n      testCases.add(new TestCase(sourceWidth, sourceHeight, targetWidth, targetHeight, apis));\n      return this;\n    }\n\n    void run() throws IOException {\n      List<String> results = new ArrayList<>();\n      for (TestCase testCase : testCases) {\n        results.addAll(testCase.test(strategy));\n      }\n\n      if (results.isEmpty()) {\n        return;\n      }\n\n      StringBuilder failure = new StringBuilder(\"Failing Tests:\\n\");\n      for (String result : results) {\n        failure.append(result).append(\"\\n\");\n      }\n      fail(failure.substring(0, failure.length() - 1));\n    }\n\n    private static final class TestCase {\n      private final int sourceWidth;\n      private final int sourceHeight;\n      private final int targetWidth;\n      private final int targetHeight;\n      private final boolean hasGainmap;\n      private final boolean allowHardwareConfig;\n      private final Api[] apis;\n\n      /**\n       * @deprecated Use the {@link Builder}.\n       */\n      @Deprecated\n      TestCase(int sourceWidth, int sourceHeight, int targetWidth, int targetHeight, Api... apis) {\n        this.sourceWidth = sourceWidth;\n        this.sourceHeight = sourceHeight;\n        this.targetWidth = targetWidth;\n        this.targetHeight = targetHeight;\n        this.hasGainmap = false;\n        this.allowHardwareConfig = false;\n        this.apis = apis;\n      }\n\n      private TestCase(Builder builder) {\n        this.sourceWidth = builder.sourceWidth;\n        this.sourceHeight = builder.sourceHeight;\n        this.targetWidth = builder.targetWidth;\n        this.targetHeight = builder.targetHeight;\n        this.hasGainmap = builder.hasGainmap;\n        this.allowHardwareConfig = builder.allowHardwareConfig;\n        this.apis = builder.apis;\n      }\n\n      List<String> test(DownsampleStrategy strategy) throws IOException {\n        List<String> results = new ArrayList<>();\n        for (Api api : apis) {\n          results.addAll(\n              api.test(\n                  sourceWidth,\n                  sourceHeight,\n                  hasGainmap,\n                  allowHardwareConfig,\n                  targetWidth,\n                  targetHeight,\n                  strategy));\n        }\n        return results;\n      }\n\n      private static final class Builder {\n\n        private int sourceWidth;\n        private int sourceHeight;\n        private int targetWidth;\n        private int targetHeight;\n        private boolean hasGainmap;\n        private boolean allowHardwareConfig;\n        @Nullable private Api[] apis;\n\n        public Builder setSourceWidth(int sourceWidth) {\n          this.sourceWidth = sourceWidth;\n          return this;\n        }\n\n        public Builder setSourceHeight(int sourceHeight) {\n          this.sourceHeight = sourceHeight;\n          return this;\n        }\n\n        public Builder setTargetWidth(int targetWidth) {\n          this.targetWidth = targetWidth;\n          return this;\n        }\n\n        public Builder setTargetHeight(int targetHeight) {\n          this.targetHeight = targetHeight;\n          return this;\n        }\n\n        public Builder setHasGainmap(boolean hasGainmap) {\n          this.hasGainmap = hasGainmap;\n          return this;\n        }\n\n        public Builder setAllowHardwareConfig(boolean allowHardwareConfig) {\n          this.allowHardwareConfig = allowHardwareConfig;\n          return this;\n        }\n\n        public Builder setApis(Api[] apis) {\n          this.apis = apis;\n          return this;\n        }\n\n        public TestCase build() {\n          Preconditions.checkNotNull(apis);\n          return new TestCase(this);\n        }\n      }\n    }\n  }\n\n  static final class Api {\n    private final int startVersion;\n    private final int stopVersion;\n    private final Formats[] formats;\n\n    static Builder apis(int min, int max) {\n      return new Builder().min(min).max(max);\n    }\n\n    static Builder atAndAbove(int min) {\n      return new Builder().min(min);\n    }\n\n    static Builder below(int max) {\n      // max is inclusive.\n      return new Builder().max(max - 1);\n    }\n\n    static Builder allApis() {\n      return new Builder();\n    }\n\n    static Api onAllApisAndAllFormatsExpect(int width, int height) {\n      return allApis().with(allFormats().expect(width, height));\n    }\n\n    static final class Builder {\n      private int maxVersion = Integer.MAX_VALUE;\n      private int minVersion = Integer.MIN_VALUE;\n\n      Builder min(int version) {\n        minVersion = version;\n        return this;\n      }\n\n      Builder max(int version) {\n        this.maxVersion = version;\n        return this;\n      }\n\n      Api with(Formats... formats) {\n        return new Api(minVersion, maxVersion, formats);\n      }\n    }\n\n    Api(int startVersion, int stopVersion, Formats... formats) {\n      this.startVersion = startVersion;\n      this.stopVersion = stopVersion;\n      this.formats = formats;\n    }\n\n    List<String> test(\n        int sourceWidth,\n        int sourceHeight,\n        boolean hasGainmap,\n        boolean allowHardwareConfig,\n        int targetWidth,\n        int targetHeight,\n        DownsampleStrategy strategy)\n        throws IOException {\n      if (Build.VERSION.SDK_INT < startVersion || Build.VERSION.SDK_INT > stopVersion) {\n        return Collections.emptyList();\n      }\n\n      List<String> results = new ArrayList<>();\n      for (Formats format : formats) {\n        results.addAll(\n            format.runTest(\n                sourceWidth,\n                sourceHeight,\n                hasGainmap,\n                allowHardwareConfig,\n                targetWidth,\n                targetHeight,\n                strategy));\n      }\n      return results;\n    }\n  }\n\n  static final class Formats {\n    private final int expectedWidth;\n    private final int expectedHeight;\n    private final CompressFormat[] formats;\n    private static final int[] ALL_EXIF_ORIENTATIONS =\n        new int[] {\n          ExifInterface.ORIENTATION_UNDEFINED,\n          ExifInterface.ORIENTATION_NORMAL,\n          ExifInterface.ORIENTATION_FLIP_HORIZONTAL,\n          ExifInterface.ORIENTATION_ROTATE_180,\n          ExifInterface.ORIENTATION_FLIP_VERTICAL,\n          ExifInterface.ORIENTATION_TRANSPOSE,\n          ExifInterface.ORIENTATION_ROTATE_90,\n          ExifInterface.ORIENTATION_TRANSVERSE,\n          ExifInterface.ORIENTATION_ROTATE_270\n        };\n    private static final int[] UNDEFINED_EXIF_ORIENTATIONS =\n        new int[] {ExifInterface.ORIENTATION_UNDEFINED};\n\n    static final class Builder {\n      private final CompressFormat[] formats;\n\n      static Builder allFormats() {\n        return formats(CompressFormat.values());\n      }\n\n      static Builder formats(CompressFormat... formats) {\n        return new Builder(formats);\n      }\n\n      Builder(CompressFormat... formats) {\n        this.formats = formats;\n      }\n\n      Formats expect(int width, int height) {\n        return new Formats(formats, width, height);\n      }\n    }\n\n    Formats(CompressFormat[] formats, int expectedWidth, int expectedHeight) {\n      this.formats = formats;\n      this.expectedWidth = expectedWidth;\n      this.expectedHeight = expectedHeight;\n    }\n\n    List<String> runTest(\n        int sourceWidth,\n        int sourceHeight,\n        boolean hasGainmap,\n        boolean allowHardwareConfig,\n        int targetWidth,\n        int targetHeight,\n        DownsampleStrategy strategy)\n        throws IOException {\n      List<String> result = new ArrayList<>();\n      for (CompressFormat format : formats) {\n        int[] exifOrientations =\n            format == CompressFormat.JPEG ? ALL_EXIF_ORIENTATIONS : UNDEFINED_EXIF_ORIENTATIONS;\n        for (int exifOrientation : exifOrientations) {\n          String testResult =\n              runScaleTest(\n                  format,\n                  sourceWidth,\n                  sourceHeight,\n                  targetWidth,\n                  targetHeight,\n                  exifOrientation,\n                  hasGainmap,\n                  allowHardwareConfig,\n                  strategy,\n                  expectedWidth,\n                  expectedHeight);\n          if (testResult != null) {\n            result.add(testResult);\n          }\n        }\n      }\n      return result;\n    }\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/load/resource/gif/GifDrawableTest.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.Manifest.permission;\nimport android.content.Context;\nimport android.os.Build;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.view.WindowManager.LayoutParams;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.rule.GrantPermissionRule;\nimport com.bumptech.glide.load.resource.gif.GifDrawable.GifState;\nimport com.bumptech.glide.load.resource.gif.GifFrameLoader.OnEveryFrameListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.test.GlideApp;\nimport com.bumptech.glide.test.ResourceIds;\nimport com.bumptech.glide.testutil.ConcurrencyHelper;\nimport com.bumptech.glide.testutil.TearDownGlide;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TestName;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class GifDrawableTest {\n  @Rule public final TestName testName = new TestName();\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  @Rule public final GrantPermissionRule grantPermissionRule;\n  private final ConcurrencyHelper concurrencyHelper = new ConcurrencyHelper();\n\n  {\n    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {\n      grantPermissionRule = GrantPermissionRule.grant(permission.SYSTEM_ALERT_WINDOW);\n    } else {\n      grantPermissionRule = GrantPermissionRule.grant();\n    }\n  }\n\n  private Context context;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test\n  public void loadGif_withInterlacedTransparentGif_sizeOriginal_succeeds()\n      throws ExecutionException, InterruptedException {\n    GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context)\n                .asGif()\n                .load(ResourceIds.raw.interlaced_transparent_gif)\n                .submit());\n    assertThat(gifDrawable).isNotNull();\n  }\n\n  @Test\n  public void loadGif_withInterlacedTransparentGif_downsampled_succeeds()\n      throws ExecutionException, InterruptedException {\n    GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context)\n                .asGif()\n                .load(ResourceIds.raw.interlaced_transparent_gif)\n                .submit(10, 10));\n    assertThat(gifDrawable).isNotNull();\n  }\n\n  @Test\n  public void loadGif_withTransparentGif_sizeOriginal_succeeds()\n      throws ExecutionException, InterruptedException {\n    GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context).asGif().load(ResourceIds.raw.transparent_gif).submit());\n    assertThat(gifDrawable).isNotNull();\n  }\n\n  @Test\n  public void loadGif_withTransparentGif_downsampled_succeeds()\n      throws ExecutionException, InterruptedException {\n    GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context).asGif().load(ResourceIds.raw.transparent_gif).submit(10, 10));\n    assertThat(gifDrawable).isNotNull();\n  }\n\n  @Test\n  public void loadGif_withOpaqueGif_sizeOriginal_succeeds()\n      throws ExecutionException, InterruptedException {\n    GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context).asGif().load(ResourceIds.raw.opaque_gif).submit());\n    assertThat(gifDrawable).isNotNull();\n  }\n\n  @Test\n  public void loadGif_withOpaqueGif_downsampled_succeeds()\n      throws ExecutionException, InterruptedException {\n    GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context).asGif().load(ResourceIds.raw.opaque_gif).submit(10, 10));\n    assertThat(gifDrawable).isNotNull();\n  }\n\n  @Test\n  public void loadGif_withOpaqueInterlacedGif_sizeOriginal_succeeds()\n      throws ExecutionException, InterruptedException {\n    GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context).asGif().load(ResourceIds.raw.opaque_interlaced_gif).submit());\n    assertThat(gifDrawable).isNotNull();\n  }\n\n  @Test\n  public void loadGif_withOpaqueInterlacedGif_downsampled_succeeds()\n      throws ExecutionException, InterruptedException {\n    GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context)\n                .asGif()\n                .load(ResourceIds.raw.opaque_interlaced_gif)\n                .submit(10, 10));\n    assertThat(gifDrawable).isNotNull();\n  }\n\n  @Test\n  public void loadGif_intoImageView_afterStop_restartsGif()\n      throws ExecutionException, InterruptedException {\n    // Mimic the state the Drawable can get into if it was loaded into a View previously and stopped\n    // so that it ended up with a pending frame that finished after the stop call.\n    final GifDrawable gifDrawable =\n        concurrencyHelper.get(\n            GlideApp.with(context)\n                .asGif()\n                .load(ResourceIds.raw.dl_world_anim)\n                .submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL));\n\n    final CountDownLatch waitForGifFrame = new CountDownLatch(1);\n    // Starting/Stopping loads in GIFs must happen on the main thread.\n    concurrencyHelper.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            // Make sure a frame is loaded while the drawable is stopped.\n            GifState gifState =\n                (GifState) Preconditions.checkNotNull(gifDrawable.getConstantState());\n            gifState.frameLoader.setOnEveryFrameReadyListener(\n                new OnEveryFrameListener() {\n                  @Override\n                  public void onFrameReady() {\n                    waitForGifFrame.countDown();\n                  }\n                });\n            gifDrawable.start();\n            gifDrawable.stop();\n          }\n        });\n    ConcurrencyHelper.waitOnLatch(waitForGifFrame);\n\n    // Load the Drawable with the pending frame into a new View and make sure it ends up in the\n    // running state.\n    final ImageView imageView = new ImageView(context);\n    concurrencyHelper.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            addViewToWindow(imageView);\n          }\n        });\n\n    concurrencyHelper.loadOnMainThread(\n        GlideApp.with(context).load(gifDrawable).override(Target.SIZE_ORIGINAL), imageView);\n\n    GifDrawable drawableFromView = (GifDrawable) imageView.getDrawable();\n    assertThat(drawableFromView.isRunning()).isTrue();\n\n    drawableFromView.stop();\n    gifDrawable.stop();\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  private void addViewToWindow(View view) {\n    WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();\n    layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;\n    layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;\n    layoutParams.type =\n        Build.VERSION.SDK_INT >= Build.VERSION_CODES.O\n            ? LayoutParams.TYPE_APPLICATION_OVERLAY\n            : Build.VERSION.SDK_INT == Build.VERSION_CODES.M\n                ? LayoutParams.TYPE_TOAST\n                : LayoutParams.TYPE_SYSTEM_ALERT;\n    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n    Preconditions.checkNotNull(windowManager).addView(view, layoutParams);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/test/BitmapRegressionTester.java",
    "content": "package com.bumptech.glide.test;\n\nimport static com.bumptech.glide.testutil.BitmapSubject.assertThat;\nimport static org.junit.Assume.assumeTrue;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.CompressFormat;\nimport android.graphics.BitmapFactory;\nimport android.os.Build;\nimport android.os.Environment;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.RequestBuilder;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.rules.TestName;\n\n/**\n * Checks for regressions for a given Glide load by comparing the result of a load to a previously\n * saved Bitmap.\n *\n * <p>Can be used to generate or re-generate expected {@link Bitmap}s by placing a file named\n * \"regenerate\" in /sdcard/DCIM/test_files. The apks containing this tester will need to have {@link\n * android.Manifest.permission#WRITE_EXTERNAL_STORAGE}. Resources can be split by apk by adding\n * {@link SplitBySdk} to test methods or classes. If {@link SplitBySdk} is added to both a test\n * class and a particular method, the values from the method will be used.\n *\n * <p>This class only handles exactly one Bitmap comparison per test method because the resource\n * names it expects and generates are based on the method name.\n */\npublic final class BitmapRegressionTester {\n  private static final String RESOURCE_TYPE = \"raw\";\n  private static final String EXTENSION = \".png\";\n  private static final String REGENERATE_SIGNAL_FILE_NAME = \"regenerate\";\n  private static final String GENERATED_FILES_DIR = \"test_files\";\n  private static final String SEPARATOR = \"_\";\n\n  private static final int RESOURCE_ID_NOT_FOUND = 0;\n\n  private final Class<?> testClass;\n  private final TestName testName;\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  public static AssumeCanRun newInstance(Class<?> testClass, TestName testName) {\n    return new AssumeCanRun(new BitmapRegressionTester(testClass, testName));\n  }\n\n  private BitmapRegressionTester(Class<?> testClass, TestName testName) {\n    this.testClass = testClass;\n    this.testName = testName;\n\n    if (testClass.getAnnotation(RegressionTest.class) == null) {\n      throw new IllegalArgumentException(\n          testClass + \" must be annotated with \" + RegressionTest.class);\n    }\n  }\n\n  public static final class AssumeCanRun {\n\n    private final BitmapRegressionTester regressionTester;\n\n    private AssumeCanRun(BitmapRegressionTester regressionTester) {\n      this.regressionTester = regressionTester;\n    }\n\n    public BitmapRegressionTester assumeShouldRun() {\n      boolean shouldRun = regressionTester.shouldRun();\n      assumeTrue(shouldRun);\n      return regressionTester;\n    }\n  }\n\n  public Bitmap test(RequestBuilder<Bitmap> request)\n      throws ExecutionException, InterruptedException {\n    Bitmap result = request.submit().get();\n    if (writeNewExpected()) {\n      writeBitmap(result);\n    }\n    Bitmap expected = decodeExpected();\n    assertThat(result).sameAs(expected);\n    return result;\n  }\n\n  private String getResourceName() {\n    return getClassNameString()\n        + SEPARATOR\n        + testName.getMethodName().toLowerCase()\n        + getSdkIntString()\n        + getCpuString();\n  }\n\n  private String getClassNameString() {\n    StringBuilder result = new StringBuilder();\n    for (char c : testClass.getSimpleName().toCharArray()) {\n      if (Character.isUpperCase(c)) {\n        result.append(Character.toLowerCase(c));\n      }\n    }\n    return result.toString();\n  }\n\n  @Nullable\n  private SplitBySdk getSplitBySdkValues() {\n    SplitBySdk result;\n    try {\n      Method method =\n          testClass.getMethod(testName.getMethodName(), /* parameterTypes...= */ (Class[]) null);\n      result = method.getAnnotation(SplitBySdk.class);\n    } catch (NoSuchMethodException e) {\n      throw new RuntimeException(e);\n    }\n\n    if (result == null) {\n      result = testClass.getAnnotation(SplitBySdk.class);\n    }\n    return result;\n  }\n\n  private String getCpuString() {\n    return splitByCpu() ? SEPARATOR + Build.CPU_ABI.replace(\"-\", \"_\") : \"\";\n  }\n\n  private boolean splitByCpu() {\n    return testClass.getAnnotation(SplitByCpu.class) != null;\n  }\n\n  private String getSdkIntString() {\n    SplitBySdk splitBySdk = getSplitBySdkValues();\n    if (splitBySdk == null) {\n      return \"\";\n    }\n    int targetSdk = -1;\n    int[] values = splitBySdk.value();\n    Arrays.sort(values);\n    for (int value : values) {\n      if (value > Build.VERSION.SDK_INT) {\n        break;\n      }\n      targetSdk = value;\n    }\n\n    if (targetSdk == -1) {\n      return \"\";\n    }\n\n    return SEPARATOR + targetSdk;\n  }\n\n  private File getTestFilesDir() {\n    File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);\n    return new File(dir, GENERATED_FILES_DIR);\n  }\n\n  private void writeBitmap(Bitmap bitmap) {\n    File testFilesDir = getTestFilesDir();\n    File subdirectory = new File(testFilesDir, RESOURCE_TYPE);\n    if (!subdirectory.exists() && !subdirectory.mkdirs()) {\n      throw new IllegalArgumentException(\"Failed to make directory: \" + subdirectory);\n    }\n\n    File file = new File(subdirectory, getResourceName() + EXTENSION);\n    if (file.exists() && !file.delete()) {\n      throw new IllegalStateException(\"Failed to remove existing file: \" + file);\n    }\n\n    OutputStream os = null;\n    try {\n      os = new BufferedOutputStream(new FileOutputStream(file));\n      bitmap.compress(CompressFormat.PNG, /* quality= */ 100, os);\n      os.close();\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    } finally {\n      if (os != null) {\n        try {\n          os.close();\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n    }\n  }\n\n  private boolean shouldRun() {\n    return writeNewExpected() || getResourceId() != RESOURCE_ID_NOT_FOUND;\n  }\n\n  private boolean writeNewExpected() {\n    File testFiles = getTestFilesDir();\n    return new File(testFiles, REGENERATE_SIGNAL_FILE_NAME).exists();\n  }\n\n  private int getResourceId() {\n    return context\n        .getResources()\n        .getIdentifier(getResourceName(), RESOURCE_TYPE, context.getPackageName());\n  }\n\n  private Bitmap decodeExpected() {\n    int resourceId = getResourceId();\n    if (resourceId == RESOURCE_ID_NOT_FOUND) {\n      throw new IllegalArgumentException(\n          \"Failed to find resource for: \"\n              + getResourceName()\n              + \" with type: \"\n              + RESOURCE_TYPE\n              + \" and package: \"\n              + context.getPackageName());\n    }\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inScaled = false;\n    return BitmapFactory.decodeResource(context.getResources(), resourceId, options);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/test/CanonicalBitmap.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.util.Preconditions;\n\npublic final class CanonicalBitmap {\n  @Nullable private Bitmap bitmap;\n  @Nullable private Float scaleFactor;\n\n  @NonNull\n  public synchronized Bitmap getBitmap() {\n    if (bitmap == null) {\n      bitmap = decodeBitmap();\n    }\n    return bitmap;\n  }\n\n  public CanonicalBitmap scale(float scaleFactor) {\n    Preconditions.checkArgument(bitmap == null, \"Can't set scale factor after decoding image\");\n    this.scaleFactor = scaleFactor;\n    return this;\n  }\n\n  public int getWidth() {\n    return getBitmap().getWidth();\n  }\n\n  public int getHeight() {\n    return getBitmap().getHeight();\n  }\n\n  private Bitmap decodeBitmap() {\n    Context context = ApplicationProvider.getApplicationContext();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inScaled = false;\n    int resourceId = ResourceIds.raw.canonical;\n    Bitmap result = BitmapFactory.decodeResource(context.getResources(), resourceId, options);\n    if (scaleFactor != null) {\n      result =\n          Bitmap.createScaledBitmap(\n              result,\n              (int) (result.getWidth() * scaleFactor),\n              (int) (result.getHeight() * scaleFactor),\n              /* filter= */ false);\n    }\n    // Make sure the Bitmap is immutable.\n    return result;\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/test/ModelGeneratorRule.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport androidx.annotation.RawRes;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.google.common.io.ByteStreams;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport org.junit.rules.ExternalResource;\n\n/** Converts raw resources into specific model types (Uris, Files, byte arrays etc). */\npublic final class ModelGeneratorRule extends ExternalResource {\n  private static final String TEMP_FOLDER_NAME = \"model_generator_rule_cache\";\n\n  private final Context context = ApplicationProvider.getApplicationContext();\n  private final AtomicInteger fileNameCounter = new AtomicInteger();\n\n  private File getTempDir() {\n    File tempDir = new File(context.getCacheDir(), TEMP_FOLDER_NAME);\n    if (!tempDir.mkdirs() && (!tempDir.exists() || !tempDir.isDirectory())) {\n      throw new IllegalStateException(\"Failed to mkdirs for: \" + tempDir);\n    }\n    return tempDir;\n  }\n\n  private File nextTempFile() {\n    String name = \"model_generator\" + fileNameCounter.getAndIncrement();\n    return new File(getTempDir(), name);\n  }\n\n  public File asFile(@RawRes int resourceId) throws IOException {\n    return writeToFile(resourceId);\n  }\n\n  public byte[] asByteArray(@RawRes int resourceId) throws IOException {\n    Resources resources = context.getResources();\n    InputStream is = resources.openRawResource(resourceId);\n    return ByteStreams.toByteArray(is);\n  }\n\n  private File writeToFile(@RawRes int resourceId) throws IOException {\n    byte[] data = asByteArray(resourceId);\n    File result = nextTempFile();\n    try (OutputStream os = new FileOutputStream(result)) {\n      os.write(data);\n    }\n    return result;\n  }\n\n  @Override\n  protected void after() {\n    super.after();\n    cleanupTempDir();\n  }\n\n  private void cleanupTempDir() {\n    File tempDir = getTempDir();\n    File[] children = tempDir.listFiles();\n    if (children != null) {\n      for (File child : children) {\n        if (child.isDirectory()) {\n          throw new IllegalStateException(\"Expected a file, but was a directory: \" + child);\n        }\n        if (!child.delete()) {\n          throw new IllegalStateException(\"Failed to delete: \" + child);\n        }\n      }\n    }\n    if (!tempDir.delete()) {\n      throw new IllegalStateException(\"Failed to delete temp dir: \" + tempDir);\n    }\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/test/RegressionTest.java",
    "content": "package com.bumptech.glide.test;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Indicates that a test is a regression test that relies on comparing a newly transformed image to\n * a previously generated copy of the same image to detect changes.\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface RegressionTest {\n  // Intentionally empty.\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/test/ResourceIds.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport androidx.test.core.app.ApplicationProvider;\n\n/**\n * Internally in google we don't appear to be able to reference resource ids directly, this class is\n * a hack around that until we figure out what's going wrong.\n */\npublic final class ResourceIds {\n  private ResourceIds() {\n    // Utility class.\n  }\n\n  public interface raw {\n    int dl_world_anim = getResourceId(\"raw\", \"dl_world_anim\");\n    int canonical = getResourceId(\"raw\", \"canonical\");\n    int canonical_large = getResourceId(\"raw\", \"canonical_large\");\n    int canonical_png = getResourceId(\"raw\", \"canonical_png\");\n    int canonical_transparent_png = getResourceId(\"raw\", \"canonical_transparent_png\");\n    int interlaced_transparent_gif = getResourceId(\"raw\", \"interlaced_transparent_gif\");\n    int transparent_gif = getResourceId(\"raw\", \"transparent_gif\");\n    int opaque_gif = getResourceId(\"raw\", \"opaque_gif\");\n    int opaque_interlaced_gif = getResourceId(\"raw\", \"opaque_interlaced_gif\");\n    int webkit_logo_p3 = getResourceId(\"raw\", \"webkit_logo_p3\");\n    int video = getResourceId(\"raw\", \"video\");\n    int animated_webp = getResourceId(\"raw\", \"dl_world_anim_webp\");\n    int animated_avif = getResourceId(\"raw\", \"dl_world_anim_avif\");\n  }\n\n  public interface drawable {\n    int bitmap_alias = getResourceId(\"drawable\", \"bitmap_alias\");\n    int googlelogo_color_120x44dp = getResourceId(\"drawable\", \"googlelogo_color_120x44dp\");\n    int shape_drawable = getResourceId(\"drawable\", \"shape_drawable\");\n    int state_list_drawable = getResourceId(\"drawable\", \"state_list_drawable\");\n    int vector_drawable = getResourceId(\"drawable\", \"vector_drawable\");\n  }\n\n  private static int getResourceId(String type, String resourceName) {\n    Context context = ApplicationProvider.getApplicationContext();\n    Resources res = context.getResources();\n    return res.getIdentifier(resourceName, type, context.getPackageName());\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/test/SplitByCpu.java",
    "content": "package com.bumptech.glide.test;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Indicates that the test relies on transformations or operations that may produce different\n * outputs on different CPUs.\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface SplitByCpu {}\n"
  },
  {
    "path": "instrumentation/src/androidTest/java/com/bumptech/glide/test/SplitBySdk.java",
    "content": "package com.bumptech.glide.test;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Used by {@link BitmapRegressionTester} to generate SDK specific resources to account for\n * differences in Android's image decoding APIs across versions.\n */\n@Target({ElementType.METHOD, ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface SplitBySdk {\n  int[] value();\n}\n"
  },
  {
    "path": "instrumentation/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n  <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n  <uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />\n  <application\n      tools:ignore=\"MissingApplicationIcon\"\n      android:theme=\"@style/AppTheme\">\n    <activity\n        android:name=\"com.bumptech.glide.test.GlideWithBeforeSuperOnCreateActivity\"\n        android:exported=\"false\" />\n    <activity\n        android:name=\"com.bumptech.glide.test.GlideWithAsDifferentSupertypesActivity\"\n        android:exported=\"false\" />\n    <activity\n        android:name=\"com.bumptech.glide.test.DefaultFragmentActivity\"\n        android:exported=\"false\" />\n    <activity\n        android:name=\"com.bumptech.glide.test.ForceDarkOrLightModeActivity\"\n        android:exported=\"false\" />\n  </application>\n</manifest>\n"
  },
  {
    "path": "instrumentation/src/main/java/com/bumptech/glide/test/DefaultFragmentActivity.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.FragmentActivity;\nimport com.bumptech.glide.instrumentation.R;\n\npublic class DefaultFragmentActivity extends FragmentActivity {\n\n  @Override\n  protected void onCreate(@Nullable Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.default_fragment_activity);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/main/java/com/bumptech/glide/test/ForceDarkOrLightModeActivity.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build.VERSION_CODES;\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.app.AppCompatDelegate;\nimport com.bumptech.glide.instrumentation.R;\nimport com.bumptech.glide.util.Preconditions;\n\npublic class ForceDarkOrLightModeActivity extends AppCompatActivity {\n  private static final int INVALID_MODE = -1;\n  private static final String ARGS_NIGHT_MODE = \"args_night_mode\";\n\n  public static Intent forceLightMode(Context context) {\n    return newArgs(context, AppCompatDelegate.MODE_NIGHT_NO);\n  }\n\n  public static Intent forceDarkMode(Context context) {\n    return newArgs(context, AppCompatDelegate.MODE_NIGHT_YES);\n  }\n\n  private static Intent newArgs(Context context, int nightMode) {\n    Intent intent = new Intent(context, ForceDarkOrLightModeActivity.class);\n    intent.putExtra(ARGS_NIGHT_MODE, nightMode);\n    return intent;\n  }\n\n  @RequiresApi(api = VERSION_CODES.JELLY_BEAN_MR1)\n  @Override\n  protected void onCreate(@Nullable Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    int modeToForce = getIntent().getExtras().getInt(ARGS_NIGHT_MODE, INVALID_MODE);\n    Preconditions.checkArgument(modeToForce != INVALID_MODE, \"Invalid mode: \" + modeToForce);\n    getDelegate().setLocalNightMode(modeToForce);\n    setContentView(R.layout.default_fragment_activity);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/main/java/com/bumptech/glide/test/GlideWithAsDifferentSupertypesActivity.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.FragmentActivity;\nimport com.bumptech.glide.Glide;\n\npublic class GlideWithAsDifferentSupertypesActivity extends FragmentActivity {\n\n  @Override\n  protected void onCreate(@Nullable Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    Glide.with(this);\n    Glide.with((Context) this);\n    Glide.with((Activity) this);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/main/java/com/bumptech/glide/test/GlideWithBeforeSuperOnCreateActivity.java",
    "content": "package com.bumptech.glide.test;\n\nimport android.os.Bundle;\nimport android.widget.TextView;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.FragmentActivity;\nimport com.bumptech.glide.Glide;\n\npublic class GlideWithBeforeSuperOnCreateActivity extends FragmentActivity {\n\n  @Override\n  protected void onCreate(@Nullable Bundle savedInstanceState) {\n    Glide.with(this);\n    super.onCreate(savedInstanceState);\n    setContentView(new TextView(this));\n  }\n\n  @Override\n  protected void onResume() {\n    super.onResume();\n    Glide.with(this);\n  }\n}\n"
  },
  {
    "path": "instrumentation/src/main/java/com/bumptech/glide/test/InstrumentationAppGlideModule.java",
    "content": "package com.bumptech.glide.test;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\n\n@GlideModule\npublic class InstrumentationAppGlideModule extends AppGlideModule {\n  // Intentionally empty.\n}\n"
  },
  {
    "path": "instrumentation/src/main/res/drawable/bitmap_alias.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:src=\"@android:drawable/star_big_off\" />\n"
  },
  {
    "path": "instrumentation/src/main/res/drawable/shape_drawable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:shape=\"rectangle\">\n  <solid android:color=\"#ffabcdef\"/>\n</shape>\n"
  },
  {
    "path": "instrumentation/src/main/res/drawable/state_list_drawable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <item android:drawable=\"@android:drawable/checkbox_on_background\"\n    android:state_pressed=\"true\" />\n  <item android:drawable=\"@android:drawable/checkbox_on_background\"\n    android:state_checked=\"true\" />\n  <item android:drawable=\"@android:drawable/checkbox_off_background\" />\n\n</selector>\n"
  },
  {
    "path": "instrumentation/src/main/res/drawable/vector_drawable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"64dp\"\n  android:height=\"64dp\"\n  android:viewportHeight=\"24.0\"\n  android:viewportWidth=\"24.0\">\n  <path\n    android:fillColor=\"@color/colorPrimary\"\n    android:pathData=\"M15.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9\n        -2,2 0.9,2 2,2zM5,12c-2.8,0 -5,2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,\n        -5 -5,-5zM5,20.5c-1.9,0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,\n        1.6 3.5,3.5 -1.6,3.5 -3.5,3.5zM10.8,10.5l2.4,-2.4 0.8,0.8c1.3,1.3\n        3,2.1 5.1,2.1L19.1,9c-1.5,0 -2.7,-0.6 -3.6,-1.5l-1.9,-1.9c-0.5,-0.4\n        -1,-0.6 -1.6,-0.6s-1.1,0.2 -1.4,0.6L7.8,8.4c-0.4,0.4 -0.6,0.9 -0.6,\n        1.4 0,0.6 0.2,1.1 0.6,1.4L11,14v5h2v-6.2l-2.2,-2.3zM19,12c-2.8,0 -5,\n        2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,-5 -5,-5zM19,20.5c-1.9,0 -3.5,-1.6\n         -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,1.6 3.5,3.5 -1.6,3.5 -3.5,3.5z\"\n      tools:ignore=\"VectorRaster\" />\n</vector>\n"
  },
  {
    "path": "instrumentation/src/main/res/drawable/vector_drawable_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"64dp\"\n  android:height=\"64dp\"\n  android:viewportHeight=\"24.0\"\n  android:viewportWidth=\"24.0\">\n  <path\n    android:fillColor=\"#048b9f\"\n    android:pathData=\"M15.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9\n        -2,2 0.9,2 2,2zM5,12c-2.8,0 -5,2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,\n        -5 -5,-5zM5,20.5c-1.9,0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,\n        1.6 3.5,3.5 -1.6,3.5 -3.5,3.5zM10.8,10.5l2.4,-2.4 0.8,0.8c1.3,1.3\n        3,2.1 5.1,2.1L19.1,9c-1.5,0 -2.7,-0.6 -3.6,-1.5l-1.9,-1.9c-0.5,-0.4\n        -1,-0.6 -1.6,-0.6s-1.1,0.2 -1.4,0.6L7.8,8.4c-0.4,0.4 -0.6,0.9 -0.6,\n        1.4 0,0.6 0.2,1.1 0.6,1.4L11,14v5h2v-6.2l-2.2,-2.3zM19,12c-2.8,0 -5,\n        2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,-5 -5,-5zM19,20.5c-1.9,0 -3.5,-1.6\n         -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,1.6 3.5,3.5 -1.6,3.5 -3.5,3.5z\" />\n</vector>\n"
  },
  {
    "path": "instrumentation/src/main/res/drawable/vector_drawable_light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:width=\"64dp\"\n  android:height=\"64dp\"\n  android:viewportHeight=\"24.0\"\n  android:viewportWidth=\"24.0\">\n  <path\n    android:fillColor=\"#f9b840\"\n    android:pathData=\"M15.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9\n        -2,2 0.9,2 2,2zM5,12c-2.8,0 -5,2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,\n        -5 -5,-5zM5,20.5c-1.9,0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,\n        1.6 3.5,3.5 -1.6,3.5 -3.5,3.5zM10.8,10.5l2.4,-2.4 0.8,0.8c1.3,1.3\n        3,2.1 5.1,2.1L19.1,9c-1.5,0 -2.7,-0.6 -3.6,-1.5l-1.9,-1.9c-0.5,-0.4\n        -1,-0.6 -1.6,-0.6s-1.1,0.2 -1.4,0.6L7.8,8.4c-0.4,0.4 -0.6,0.9 -0.6,\n        1.4 0,0.6 0.2,1.1 0.6,1.4L11,14v5h2v-6.2l-2.2,-2.3zM19,12c-2.8,0 -5,\n        2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,-5 -5,-5zM19,20.5c-1.9,0 -3.5,-1.6\n         -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,1.6 3.5,3.5 -1.6,3.5 -3.5,3.5z\" />\n</vector>\n"
  },
  {
    "path": "instrumentation/src/main/res/layout/default_fragment_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />"
  },
  {
    "path": "instrumentation/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n  <color name=\"colorPrimary\">#f9b840</color>\n</resources>"
  },
  {
    "path": "instrumentation/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "instrumentation/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <style name=\"AppTheme\" parent=\"Theme.AppCompat.DayNight.DarkActionBar\">\n    <!-- Customize your theme here. -->\n    <item name=\"colorPrimary\">@color/colorPrimary</item>\n  </style>\n</resources>"
  },
  {
    "path": "instrumentation/src/main/res/values-night/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"colorPrimary\">#048b9f</color>\n</resources>"
  },
  {
    "path": "integration/avif/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.avif\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    implementation(libs.avif)\n    implementation(libs.guava)\n\n    annotationProcessor(project(\":annotation:compiler\"))\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/avif/gradle.properties",
    "content": "POM_NAME=Glide AVIF Integration\nPOM_ARTIFACT_ID=avif-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to support AVIF images in Glide\n"
  },
  {
    "path": "integration/avif/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n</lint>\n"
  },
  {
    "path": "integration/avif/src/main/java/com/bumptech/glide/integration/avif/AvifByteBufferBitmapDecoder.java",
    "content": "package com.bumptech.glide.integration.avif;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\nimport android.util.Log;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.bitmap.BitmapResource;\nimport com.bumptech.glide.load.resource.bitmap.Downsampler;\nimport com.bumptech.glide.util.Preconditions;\nimport java.nio.ByteBuffer;\nimport javax.annotation.Nullable;\nimport org.aomedia.avif.android.AvifDecoder;\nimport org.aomedia.avif.android.AvifDecoder.Info;\n\n/** A Glide {@link ResourceDecoder} capable of decoding Avif images. */\npublic final class AvifByteBufferBitmapDecoder implements ResourceDecoder<ByteBuffer, Bitmap> {\n  private static final String TAG = \"AvifBitmapDecoder\";\n\n  private final BitmapPool bitmapPool;\n\n  public AvifByteBufferBitmapDecoder(BitmapPool bitmapPool) {\n    this.bitmapPool = Preconditions.checkNotNull(bitmapPool);\n  }\n\n  private ByteBuffer maybeCopyBuffer(ByteBuffer source) {\n    // Native calls can only access ByteBuffer if isDirect() is true. Otherwise, we would have to\n    // make a copy into a direct ByteBuffer.\n    if (source.isDirect()) {\n      return source;\n    }\n    ByteBuffer sourceCopy = ByteBuffer.allocateDirect(source.remaining());\n    sourceCopy.put(source);\n    sourceCopy.flip();\n    return sourceCopy;\n  }\n\n  @Override\n  @Nullable\n  public Resource<Bitmap> decode(ByteBuffer source, int width, int height, Options options) {\n    ByteBuffer sourceCopy = maybeCopyBuffer(source);\n    Info info = new Info();\n    if (!AvifDecoder.getInfo(sourceCopy, sourceCopy.remaining(), info)) {\n      if (Log.isLoggable(TAG, Log.ERROR)) {\n        Log.e(TAG, \"Requested to decode byte buffer which cannot be handled by AvifDecoder\");\n      }\n      return null;\n    }\n    Bitmap.Config config;\n    if (options.get(Downsampler.DECODE_FORMAT) == DecodeFormat.PREFER_RGB_565) {\n      config = Config.RGB_565;\n    } else {\n      config = (info.depth == 8) ? Config.ARGB_8888 : Config.RGBA_F16;\n    }\n    Bitmap bitmap = bitmapPool.get(info.width, info.height, config);\n    if (!AvifDecoder.decode(sourceCopy, sourceCopy.remaining(), bitmap)) {\n      if (Log.isLoggable(TAG, Log.ERROR)) {\n        Log.e(TAG, \"Failed to decode ByteBuffer as Avif.\");\n      }\n      bitmapPool.put(bitmap);\n      return null;\n    }\n    return BitmapResource.obtain(bitmap, bitmapPool);\n  }\n\n  @Override\n  public boolean handles(ByteBuffer source, Options options) {\n    return AvifDecoder.isAvifImage(maybeCopyBuffer(source));\n  }\n}\n"
  },
  {
    "path": "integration/avif/src/main/java/com/bumptech/glide/integration/avif/AvifGlideModule.java",
    "content": "package com.bumptech.glide.integration.avif;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.load.resource.bitmap.BitmapDrawableDecoder;\nimport com.bumptech.glide.module.LibraryGlideModule;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\n\n/** Glide support for AVIF Images. */\n@GlideModule\npublic final class AvifGlideModule extends LibraryGlideModule {\n\n  @Override\n  public void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    // Add the Avif ResourceDecoders before any of the available system decoders. This ensures that\n    // the integration will be preferred for Avif images.\n    AvifByteBufferBitmapDecoder byteBufferBitmapDecoder =\n        new AvifByteBufferBitmapDecoder(glide.getBitmapPool());\n    registry.prepend(\n        Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder);\n    registry.prepend(\n        Registry.BUCKET_BITMAP_DRAWABLE,\n        ByteBuffer.class,\n        BitmapDrawable.class,\n        new BitmapDrawableDecoder<>(context.getResources(), byteBufferBitmapDecoder));\n    AvifStreamBitmapDecoder streamBitmapDecoder =\n        new AvifStreamBitmapDecoder(\n            registry.getImageHeaderParsers(), byteBufferBitmapDecoder, glide.getArrayPool());\n    registry.prepend(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder);\n    registry.prepend(\n        Registry.BUCKET_BITMAP_DRAWABLE,\n        InputStream.class,\n        BitmapDrawable.class,\n        new BitmapDrawableDecoder<>(context.getResources(), streamBitmapDecoder));\n  }\n}\n"
  },
  {
    "path": "integration/avif/src/main/java/com/bumptech/glide/integration/avif/AvifStreamBitmapDecoder.java",
    "content": "package com.bumptech.glide.integration.avif;\n\nimport android.graphics.Bitmap;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.ImageHeaderParserUtils;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/** A Glide {@link ResourceDecoder} capable of decoding Avif Images. */\npublic final class AvifStreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {\n  private static final String TAG = \"AvifStreamBitmapDecoder\";\n\n  private final List<ImageHeaderParser> parsers;\n  private final AvifByteBufferBitmapDecoder avifByteBufferDecoder;\n  private final ArrayPool arrayPool;\n\n  public AvifStreamBitmapDecoder(\n      List<ImageHeaderParser> parsers,\n      AvifByteBufferBitmapDecoder avifByteBufferDecoder,\n      ArrayPool arrayPool) {\n    this.parsers = parsers;\n    this.avifByteBufferDecoder = Preconditions.checkNotNull(avifByteBufferDecoder);\n    this.arrayPool = Preconditions.checkNotNull(arrayPool);\n  }\n\n  @Override\n  @Nullable\n  public Resource<Bitmap> decode(InputStream source, int width, int height, Options options)\n      throws IOException {\n    return avifByteBufferDecoder.decode(ByteBufferUtil.fromStream(source), width, height, options);\n  }\n\n  @Override\n  public boolean handles(InputStream source, Options options) throws IOException {\n    ImageType type = ImageHeaderParserUtils.getType(parsers, source, arrayPool);\n    return type.equals(ImageType.AVIF) || type.equals(ImageType.ANIMATED_AVIF);\n  }\n}\n"
  },
  {
    "path": "integration/build.gradle.kts",
    "content": "// keep an empty file to make sure Gradle recognizes the properties\n"
  },
  {
    "path": "integration/compose/api/compose.api",
    "content": "public abstract interface annotation class com/bumptech/glide/integration/compose/ExperimentalGlideComposeApi : java/lang/annotation/Annotation {\n}\n\npublic final class com/bumptech/glide/integration/compose/GlideImageKt {\n\tpublic static final fun GlideImage (Ljava/lang/Object;Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/ColorFilter;Lcom/bumptech/glide/integration/compose/Placeholder;Lcom/bumptech/glide/integration/compose/Placeholder;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V\n\tpublic static final fun placeholder (I)Lcom/bumptech/glide/integration/compose/Placeholder;\n\tpublic static final fun placeholder (Landroid/graphics/drawable/Drawable;)Lcom/bumptech/glide/integration/compose/Placeholder;\n\tpublic static final fun placeholder (Lkotlin/jvm/functions/Function2;)Lcom/bumptech/glide/integration/compose/Placeholder;\n}\n\npublic abstract interface class com/bumptech/glide/integration/compose/GlidePreloadingData {\n\tpublic abstract fun get (ILandroidx/compose/runtime/Composer;I)Lkotlin/Pair;\n\tpublic abstract fun getSize ()I\n}\n\npublic abstract class com/bumptech/glide/integration/compose/Placeholder {\n\tpublic static final field $stable I\n}\n\npublic final class com/bumptech/glide/integration/compose/PreloadKt {\n\tpublic static final fun rememberGlidePreloadingData-Z8o_i8w (Ljava/util/List;JILjava/lang/Integer;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)Lcom/bumptech/glide/integration/compose/GlidePreloadingData;\n\tpublic static final fun rememberGlidePreloadingData-u6VnWhU (ILkotlin/jvm/functions/Function1;JILjava/lang/Integer;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)Lcom/bumptech/glide/integration/compose/GlidePreloadingData;\n}\n\n"
  },
  {
    "path": "integration/compose/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    id(\"com.android.library\")\n    id(\"org.jetbrains.kotlin.android\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.compose\"\n    compileSdk = 34\n\n    defaultConfig {\n        minSdk = 21\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildFeatures { compose = true }\n\n    buildTypes { getByName(\"release\") { isMinifyEnabled = false } }\n\n    composeOptions {\n        kotlinCompilerExtensionVersion = libs.versions.kotlin.compiler.extension.get()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n\n    kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_1_8) } }\n\n    testOptions { unitTests { isIncludeAndroidResources = true } }\n}\n\ntasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {\n    if (!name.contains(\"Test\")) {\n        kotlinOptions.freeCompilerArgs += \"-Xexplicit-api=strict\"\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    implementation(project(\":integration:ktx\"))\n\n    implementation(project(\":integration:recyclerview\")) { isTransitive = false }\n\n    implementation(libs.compose.foundation)\n    implementation(libs.compose.ui)\n    implementation(libs.drawablepainter)\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.lifecycle.runtime.compose)\n    debugImplementation(libs.compose.ui.testmanifest)\n    testImplementation(libs.compose.ui.testmanifest)\n    testImplementation(libs.compose.ui.testjunit4)\n    testImplementation(libs.junit)\n    testImplementation(libs.robolectric)\n    testImplementation(libs.androidx.appcompat)\n    testImplementation(libs.androidx.junit)\n    testImplementation(libs.androidx.test.runner)\n    testImplementation(libs.androidx.lifecycle.runtime.testing)\n    androidTestImplementation(libs.junit)\n    androidTestImplementation(libs.compose.ui.testjunit4)\n    androidTestImplementation(libs.androidx.espresso)\n    androidTestImplementation(libs.androidx.espresso.idling)\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.compose.material)\n    androidTestImplementation(libs.truth)\n    androidTestImplementation(project(\":testutil\"))\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")\n"
  },
  {
    "path": "integration/compose/gradle.properties",
    "content": "POM_NAME=Glide Compose Integration\nPOM_ARTIFACT_ID=compose\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to integrate with Jetpack Compose\n\nVERSION_MAJOR=1\nVERSION_MINOR=0\nVERSION_PATCH=0\nVERSION_NAME=1.0.0-beta08"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/GlideImageCustomDrawableTransformationTest.kt",
    "content": "@file:OptIn(ExperimentalGlideComposeApi::class, ExperimentalCoroutinesApi::class)\n\npackage com.bumptech.glide.integration.compose\n\nimport android.graphics.Canvas\nimport android.graphics.ColorFilter\nimport android.graphics.drawable.Animatable\nimport android.graphics.drawable.Drawable\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.layout.ScaleFactor\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.lifecycle.testing.TestLifecycleOwner\nimport com.bumptech.glide.integration.compose.test.Constants\nimport com.bumptech.glide.integration.compose.test.GlideComposeRule\nimport com.bumptech.glide.integration.compose.test.assertDisplaysInstance\nimport com.bumptech.glide.integration.compose.test.onNodeWithDefaultContentDescription\nimport com.google.common.truth.Truth.assertThat\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.junit.runners.Parameterized\n\n/**\n * Tests Issue #4943.\n *\n * Transformable types are tested in [GlideImageDefaultTransformationTest].\n */\n@RunWith(Parameterized::class)\nclass GlideImageCustomDrawableTransformationTest(\n    private val contentScale: ContentScale,\n    // We need a shorter test name than the ContentScale class name to make google3 happy, so we\n    // add an extra parameter. Unfortunately that means we need to list it in the constructor even\n    // though it's only used by Parameters to create the test name.\n    @Suppress(\"unused\") private val name: String,\n) {\n    @get:Rule val glideComposeRule = GlideComposeRule()\n\n    @Test\n    fun glideImage_nonBitmapDrawable_doesNotThrow() = runTest {\n        val customDrawable = FakeDrawable()\n\n        glideComposeRule.setContent { GlideImageWithCustomDrawable(customDrawable) }\n\n        glideComposeRule\n            .onNodeWithDefaultContentDescription()\n            .assertDisplaysInstance(customDrawable)\n    }\n\n    @Test\n    fun glideImage_animatableDrawable_doesNotThrow() = runTest {\n        val customDrawable = FakeAnimatableDrawable()\n\n        glideComposeRule.setContent { GlideImageWithCustomDrawable(customDrawable) }\n\n        glideComposeRule\n            .onNodeWithDefaultContentDescription()\n            .assertDisplaysInstance(customDrawable)\n    }\n\n    @Test\n    fun glideImage_animatableDrawable_stopsAnimationWhenLifecycleNotStarted() = runTest {\n        val customDrawable = FakeAnimatableDrawable()\n        val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.STARTED)\n\n        glideComposeRule.setContent {\n            CompositionLocalProvider(LocalLifecycleOwner provides testLifecycleOwner) {\n                GlideImageWithCustomDrawable(customDrawable)\n            }\n        }\n        assertThat(customDrawable.animating).isTrue()\n        testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_STOP)\n        assertThat(customDrawable.animating).isFalse()\n    }\n\n    @Composable\n    private fun GlideImageWithCustomDrawable(customDrawable: FakeDrawable) {\n        GlideImage(\n            model = customDrawable,\n            contentScale = contentScale,\n            contentDescription = Constants.DEFAULT_DESCRIPTION,\n            modifier = Modifier.size(200.dp, 100.dp),\n        )\n    }\n\n    companion object {\n        // Add a second parameter purely to make the test name shorter, see the comment on the test\n        // constructor argument for details.\n        @JvmStatic\n        @Parameterized.Parameters(name = \"{1}\")\n        fun data() =\n            arrayOf(\n                arrayOf(ContentScale.Crop, \"Crop\"),\n                arrayOf(ContentScale.FillBounds, \"FillBounds\"),\n                arrayOf(ContentScale.FillHeight, \"FillHeight\"),\n                arrayOf(ContentScale.FillWidth, \"FillWidth\"),\n                arrayOf(ContentScale.Fit, \"Fit\"),\n                arrayOf(ContentScale.Inside, \"Inside\"),\n                arrayOf(ContentScale.None, \"None\"),\n                arrayOf(\n                    object : ContentScale {\n                        override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor =\n                            ContentScale.Fit.computeScaleFactor(srcSize, dstSize)\n                    },\n                    \"Custom\",\n                ),\n            )\n    }\n}\n\n@Suppress(\"DeprecatedCallableAddReplaceWith\")\nprivate open class FakeDrawable : Drawable() {\n    override fun draw(p0: Canvas) {}\n\n    override fun setAlpha(p0: Int) = throw UnsupportedOperationException()\n\n    override fun setColorFilter(p0: ColorFilter?) = throw UnsupportedOperationException()\n\n    @Deprecated(\"Deprecated in Java\")\n    override fun getOpacity(): Int = throw UnsupportedOperationException()\n}\n\nprivate class FakeAnimatableDrawable : FakeDrawable(), Animatable {\n    var animating: Boolean? = null\n\n    override fun start() {\n        animating = true\n    }\n\n    override fun stop() {\n        animating = false\n    }\n\n    override fun isRunning(): Boolean = throw UnsupportedOperationException()\n}\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/GlideImageDefaultTransformationTest.kt",
    "content": "@file:OptIn(\n    ExperimentalCoroutinesApi::class,\n    ExperimentGlideFlows::class,\n    ExperimentalGlideComposeApi::class,\n)\n\npackage com.bumptech.glide.integration.compose\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.DrawableRes\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.unit.dp\nimport androidx.test.core.app.ApplicationProvider\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.integration.compose.test.Constants\nimport com.bumptech.glide.integration.compose.test.GlideComposeRule\nimport com.bumptech.glide.integration.compose.test.assertDisplays\nimport com.bumptech.glide.integration.compose.test.dpToPixels\nimport com.bumptech.glide.integration.compose.test.onNodeWithDefaultContentDescription\nimport com.bumptech.glide.integration.ktx.ExperimentGlideFlows\nimport com.bumptech.glide.integration.ktx.Resource\nimport com.bumptech.glide.integration.ktx.Status\nimport com.bumptech.glide.integration.ktx.flow\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Rule\nimport org.junit.Test\n\n/** Non-transformable types are tested in [GlideImageCustomDrawableTransformationTest] */\nclass GlideImageDefaultTransformationTest {\n    private val context: Context = ApplicationProvider.getApplicationContext()\n    @get:Rule val glideComposeRule = GlideComposeRule()\n\n    @Test\n    fun glideImage_withContentScaleNone_noTransformation_doesNotApplyTransformation() = runTest {\n        val resourceId = android.R.drawable.star_big_on\n        val expectedDrawable = loadExpectedDrawable(resourceId)\n\n        glideComposeRule.setContent {\n            ContentScaleGlideImage(model = resourceId, contentScale = ContentScale.None)\n        }\n\n        glideComposeRule.onNodeWithDefaultContentDescription().assertDisplays(expectedDrawable)\n    }\n\n    @Test\n    fun glideImage_withContentScaleFit_noTransformation_appliesCenterInsideTransformation() =\n        runTest {\n            val resourceId = android.R.drawable.star_big_on\n            val expectedDrawable = loadExpectedDrawable(resourceId) { it.centerInside() }\n\n            glideComposeRule.setContent {\n                ContentScaleGlideImage(model = resourceId, contentScale = ContentScale.Fit)\n            }\n\n            glideComposeRule.onNodeWithDefaultContentDescription().assertDisplays(expectedDrawable)\n        }\n\n    @Test\n    fun glideImage_withContentScaleFit_explicitTransformation_usesExplicitTransformation() =\n        runTest {\n            val resourceId = android.R.drawable.star_big_on\n            val expectedDrawable = loadExpectedDrawable(resourceId) { it.centerCrop() }\n\n            glideComposeRule.setContent {\n                ContentScaleGlideImage(model = resourceId, contentScale = ContentScale.Fit) {\n                    it.centerCrop()\n                }\n            }\n\n            glideComposeRule.onNodeWithDefaultContentDescription().assertDisplays(expectedDrawable)\n        }\n\n    @Test\n    fun glideImage_withContentScaleInside_noTransformation_appliesCenterInsideTransformation() =\n        runTest {\n            val resourceId = android.R.drawable.star_big_on\n            val expectedDrawable = loadExpectedDrawable(resourceId) { it.centerInside() }\n\n            glideComposeRule.setContent {\n                ContentScaleGlideImage(model = resourceId, contentScale = ContentScale.Inside)\n            }\n\n            glideComposeRule.onNodeWithDefaultContentDescription().assertDisplays(expectedDrawable)\n        }\n\n    @Test\n    fun glideImage_withContentScaleInside_explicitTransformation_usesExplicitTransformation() =\n        runTest {\n            val resourceId = android.R.drawable.star_big_on\n            val expectedDrawable = loadExpectedDrawable(resourceId) { it.centerCrop() }\n\n            glideComposeRule.setContent {\n                ContentScaleGlideImage(model = resourceId, contentScale = ContentScale.Inside) {\n                    it.centerCrop()\n                }\n            }\n\n            glideComposeRule.onNodeWithDefaultContentDescription().assertDisplays(expectedDrawable)\n        }\n\n    @Test\n    fun glideImage_withContentScaleCrop_noTransformation_appliesCenterCropTransformation() =\n        runTest {\n            val resourceId = android.R.drawable.star_big_on\n            val expectedDrawable = loadExpectedDrawable(resourceId) { it.centerCrop() }\n\n            glideComposeRule.setContent {\n                ContentScaleGlideImage(model = resourceId, contentScale = ContentScale.Crop)\n            }\n\n            glideComposeRule.onNodeWithDefaultContentDescription().assertDisplays(expectedDrawable)\n        }\n\n    @Test\n    fun glideImage_withContentScaleCrop_explicitTransformation_usesExplicitTransformation() =\n        runTest {\n            val resourceId = android.R.drawable.star_big_on\n            val expectedDrawable = loadExpectedDrawable(resourceId) { it.centerInside() }\n\n            glideComposeRule.setContent {\n                ContentScaleGlideImage(model = resourceId, contentScale = ContentScale.Crop) {\n                    it.centerInside()\n                }\n            }\n\n            glideComposeRule.onNodeWithDefaultContentDescription().assertDisplays(expectedDrawable)\n        }\n\n    private suspend fun RequestBuilder<Drawable>.loadRequiringSuccess() =\n        (this.flow().first { it.status == Status.SUCCEEDED } as Resource<Drawable>).resource\n\n    private suspend fun loadExpectedDrawable(\n        @DrawableRes resourceId: Int,\n        transformation: (RequestBuilder<Drawable>) -> RequestBuilder<Drawable> = { it -> it },\n    ): Drawable =\n        transformation(\n                Glide.with(context)\n                    .load(resourceId)\n                    .override(WIDTH.dpToPixels(), HEIGHT.dpToPixels())\n            )\n            .loadRequiringSuccess()\n\n    @Composable\n    private fun ContentScaleGlideImage(\n        model: Any?,\n        contentScale: ContentScale,\n        requestBuilderTransform: RequestBuilderTransform<Drawable> = { it -> it },\n    ) =\n        GlideImage(\n            model = model,\n            contentDescription = Constants.DEFAULT_DESCRIPTION,\n            modifier = SIZE_MODIFIER,\n            contentScale = contentScale,\n            requestBuilderTransform = requestBuilderTransform,\n        )\n\n    companion object {\n        const val WIDTH = 25\n        // non-square\n        const val HEIGHT = 30\n\n        val SIZE_MODIFIER = Modifier.size(WIDTH.dp, HEIGHT.dp)\n    }\n}\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/GlideImageErrorTest.kt",
    "content": "@file:OptIn(ExperimentalGlideComposeApi::class)\n\npackage com.bumptech.glide.integration.compose\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport androidx.compose.ui.test.assert\nimport androidx.compose.ui.test.onNodeWithContentDescription\nimport androidx.test.core.app.ApplicationProvider\nimport com.bumptech.glide.integration.compose.test.GlideComposeRule\nimport com.bumptech.glide.integration.compose.test.expectDisplayedDrawable\nimport com.bumptech.glide.integration.compose.test.expectDisplayedResource\nimport com.bumptech.glide.integration.compose.test.expectNoDrawable\nimport org.junit.Rule\nimport org.junit.Test\n\n/**\n * Avoids [com.bumptech.glide.load.engine.executor.GlideIdlingResourceInit] because we want to make\n * assertions about loads that have not yet completed.\n */\nclass GlideImageErrorTest {\n    private val context: Context = ApplicationProvider.getApplicationContext()\n    @get:Rule val glideComposeRule = GlideComposeRule()\n\n    @Test\n    fun requestBuilderTransform_withErrorResourceId_displaysError() {\n        val description = \"test\"\n        val errorResourceId = android.R.drawable.star_big_off\n        glideComposeRule.setContent {\n            GlideImage(model = null, contentDescription = description) { it.error(errorResourceId) }\n        }\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(errorResourceId))\n    }\n\n    @Test\n    fun requestBuilderTransform_withErrorDrawable_displaysError() {\n        val description = \"test\"\n        val errorDrawable = context.getDrawable(android.R.drawable.star_big_off)\n        glideComposeRule.setContent {\n            GlideImage(model = null, contentDescription = description) { it.error(errorDrawable) }\n        }\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawable(errorDrawable))\n    }\n\n    @Test\n    fun failureParameter_withErrorResourceId_displaysError() {\n        val description = \"test\"\n        val failureResourceId = android.R.drawable.star_big_off\n        glideComposeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = description,\n                failure = placeholder(failureResourceId),\n            )\n        }\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(failureResourceId))\n    }\n\n    @Test\n    fun failureParameter_withDrawable_displaysDrawable() {\n        val description = \"test\"\n        val failureDrawable = context.getDrawable(android.R.drawable.star_big_off)\n        glideComposeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = description,\n                failure = placeholder(failureDrawable),\n            )\n        }\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawable(failureDrawable))\n    }\n\n    @Test\n    fun failureParameter_withNullDrawable_displaysNothing() {\n        val description = \"test\"\n        glideComposeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = description,\n                failure = placeholder(null as Drawable?),\n            )\n        }\n\n        glideComposeRule.onNodeWithContentDescription(description).assert(expectNoDrawable())\n    }\n\n    @Test\n    fun failureParameter_withComposable_displaysComposable() {\n        val failureResourceId = android.R.drawable.star_big_off\n        val description = \"test\"\n        glideComposeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = \"none\",\n                failure =\n                    placeholder {\n                        // Nesting GlideImage is not really a good idea, but it's convenient for\n                        // this test\n                        // because\n                        // we can use our helpers to assert on its contents.\n                        GlideImage(\n                            model = null,\n                            contentDescription = description,\n                            failure = placeholder(failureResourceId),\n                        )\n                    },\n            )\n        }\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(failureResourceId))\n    }\n\n    @Test\n    fun failure_setViaFailureParameterWithResourceId_andRequestBuilderTransform_prefersFailureParameter() {\n        val description = \"test\"\n        val failureResourceId = android.R.drawable.star_big_off\n        glideComposeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = description,\n                failure = placeholder(failureResourceId),\n            ) {\n                it.error(android.R.drawable.btn_star)\n            }\n        }\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(failureResourceId))\n    }\n\n    @Test\n    fun failure_setViaFailureParameterWithDrawable_andRequestBuilderTransform_prefersFailureParameter() {\n        val description = \"test\"\n        val failureDrawable = context.getDrawable(android.R.drawable.star_big_off)\n        glideComposeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = description,\n                failure = placeholder(failureDrawable),\n            ) {\n                it.error(android.R.drawable.btn_star)\n            }\n        }\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawable(failureDrawable))\n    }\n\n    @Test\n    fun failure_setViaFailureParameterWithNullDrawable_andRequestBuilderTransformWithNonNullDrawable_showsNoPlaceholder() {\n        val description = \"test\"\n        glideComposeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = description,\n                failure = placeholder(null as Drawable?),\n            ) {\n                it.error(android.R.drawable.btn_star)\n            }\n        }\n\n        glideComposeRule.onNodeWithContentDescription(description).assert(expectNoDrawable())\n    }\n\n    @Test\n    fun failure_setViaFailureParameterWithComposable_andRequestBuilderTransform_showsComposable() {\n        val description = \"test\"\n        val failureResourceId = android.R.drawable.star_big_off\n        glideComposeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = \"other\",\n                failure =\n                    placeholder {\n                        GlideImage(\n                            model = null,\n                            contentDescription = description,\n                            failure = placeholder(failureResourceId),\n                        )\n                    },\n            ) {\n                it.error(android.R.drawable.btn_star)\n            }\n        }\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(failureResourceId))\n    }\n}\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/GlideImagePlaceholderTest.kt",
    "content": "@file:OptIn(ExperimentalGlideComposeApi::class)\n\npackage com.bumptech.glide.integration.compose\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport androidx.compose.ui.test.assert\nimport androidx.compose.ui.test.junit4.createComposeRule\nimport androidx.compose.ui.test.onNodeWithContentDescription\nimport androidx.test.core.app.ApplicationProvider\nimport com.bumptech.glide.integration.compose.test.expectDisplayedDrawable\nimport com.bumptech.glide.integration.compose.test.expectDisplayedResource\nimport com.bumptech.glide.integration.compose.test.expectNoDrawable\nimport com.bumptech.glide.testutil.TearDownGlide\nimport com.bumptech.glide.testutil.WaitModelLoaderRule\nimport org.junit.Rule\nimport org.junit.Test\n\n/**\n * Avoids [com.bumptech.glide.load.engine.executor.GlideIdlingResourceInit] and\n * [com.bumptech.glide.integration.compose.test.GlideComposeRule] because we want to make assertions\n * about loads that have not yet completed.\n */\nclass GlideImagePlaceholderTest {\n    private val context: Context = ApplicationProvider.getApplicationContext()\n    @get:Rule(order = 1) val composeRule = createComposeRule()\n    @get:Rule(order = 2) val waitModelLoaderRule = WaitModelLoaderRule()\n    @get:Rule(order = 3) val tearDownGlide = TearDownGlide()\n\n    @Test\n    fun requestBuilderTransform_withPlaceholderResourceId_displaysPlaceholder() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        val placeholderResourceId = android.R.drawable.star_big_off\n        composeRule.setContent {\n            GlideImage(model = waitModel, contentDescription = description) {\n                it.placeholder(placeholderResourceId)\n            }\n        }\n\n        composeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(placeholderResourceId))\n    }\n\n    @Test\n    fun requestBuilderTransform_withPlaceholderDrawable_displaysPlaceholder() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        val placeholderDrawable = context.getDrawable(android.R.drawable.star_big_off)\n        composeRule.setContent {\n            GlideImage(model = waitModel, contentDescription = description) {\n                it.placeholder(placeholderDrawable)\n            }\n        }\n\n        composeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawable(placeholderDrawable))\n    }\n\n    @Test\n    fun loadingParameter_withResourceId_displaysResource() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        val placeholderResourceId = android.R.drawable.star_big_off\n        composeRule.setContent {\n            GlideImage(\n                model = waitModel,\n                contentDescription = description,\n                loading = placeholder(placeholderResourceId),\n            )\n        }\n\n        composeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(placeholderResourceId))\n    }\n\n    @Test\n    fun loadingParameter_withDrawable_displaysResource() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        val placeholderDrawable = context.getDrawable(android.R.drawable.star_big_off)\n        composeRule.setContent {\n            GlideImage(\n                model = waitModel,\n                contentDescription = description,\n                loading = placeholder(placeholderDrawable),\n            )\n        }\n\n        composeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawable(placeholderDrawable))\n    }\n\n    @Test\n    fun loadingParameter_withNullDrawable_displaysNothing() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        composeRule.setContent {\n            GlideImage(\n                model = waitModel,\n                contentDescription = description,\n                loading = placeholder(null as Drawable?),\n            )\n        }\n\n        composeRule.onNodeWithContentDescription(description).assert(expectNoDrawable())\n    }\n\n    @Test\n    fun loadingParameter_withComposable_displaysComposable() {\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        val placeholderResourceId = android.R.drawable.star_big_off\n        val description = \"test\"\n        composeRule.setContent {\n            GlideImage(\n                model = waitModel,\n                contentDescription = \"none\",\n                loading =\n                    placeholder {\n                        // Nesting GlideImage is not really a good idea, but it's convenient for\n                        // this test\n                        // because\n                        // we can use our helpers to assert on its contents.\n                        GlideImage(\n                            model = waitModel,\n                            contentDescription = description,\n                            loading = placeholder(placeholderResourceId),\n                        )\n                    },\n            )\n        }\n\n        composeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(placeholderResourceId))\n    }\n\n    @Test\n    fun loading_setViaLoadingParameterWithResourceId_andRequestBuilderTransform_prefersLoadingParameter() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        val placeholderResourceId = android.R.drawable.star_big_off\n        composeRule.setContent {\n            GlideImage(\n                model = waitModel,\n                contentDescription = description,\n                loading = placeholder(placeholderResourceId),\n            ) {\n                it.placeholder(android.R.drawable.btn_star)\n            }\n        }\n\n        composeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(placeholderResourceId))\n    }\n\n    @Test\n    fun loading_setViaLoadingParameterWithDrawable_andRequestBuilderTransform_prefersLoadingParameter() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        val placeholderDrawable = context.getDrawable(android.R.drawable.star_big_off)\n        composeRule.setContent {\n            GlideImage(\n                model = waitModel,\n                contentDescription = description,\n                loading = placeholder(placeholderDrawable),\n            ) {\n                it.placeholder(android.R.drawable.btn_star)\n            }\n        }\n\n        composeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawable(placeholderDrawable))\n    }\n\n    @Test\n    fun loading_setViaLoadingParameterWithNullDrawable_andRequestBuilderTransform_showsNoResource() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        composeRule.setContent {\n            GlideImage(\n                model = waitModel,\n                contentDescription = description,\n                loading = placeholder(null as Drawable?),\n            ) {\n                it.placeholder(android.R.drawable.btn_star)\n            }\n        }\n\n        composeRule.onNodeWithContentDescription(description).assert(expectNoDrawable())\n    }\n\n    @Test\n    fun loading_setViaLoadingParameterWithComposable_andRequestBuilderTransform_showsComposable() {\n        val description = \"test\"\n        val waitModel = waitModelLoaderRule.waitOn(android.R.drawable.star_big_on)\n        val placeholderResourceId = android.R.drawable.star_big_off\n        composeRule.setContent {\n            GlideImage(\n                model = waitModel,\n                contentDescription = \"other\",\n                loading =\n                    placeholder {\n                        GlideImage(\n                            model = waitModel,\n                            contentDescription = description,\n                            loading = placeholder(placeholderResourceId),\n                        )\n                    },\n            ) {\n                it.placeholder(android.R.drawable.btn_star)\n            }\n        }\n\n        composeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedResource(placeholderResourceId))\n    }\n}\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/GlideImageTest.kt",
    "content": "@file:OptIn(ExperimentalGlideComposeApi::class, InternalGlideApi::class)\n\npackage com.bumptech.glide.integration.compose\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.material.Text\nimport androidx.compose.material.TextButton\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.test.assert\nimport androidx.compose.ui.test.onNodeWithContentDescription\nimport androidx.compose.ui.test.onNodeWithText\nimport androidx.compose.ui.test.performClick\nimport androidx.compose.ui.unit.dp\nimport androidx.test.core.app.ApplicationProvider\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.integration.compose.test.GlideComposeRule\nimport com.bumptech.glide.integration.compose.test.assertDisplays\nimport com.bumptech.glide.integration.compose.test.bitmapSize\nimport com.bumptech.glide.integration.compose.test.dpToPixels\nimport com.bumptech.glide.integration.compose.test.expectDisplayedDrawable\nimport com.bumptech.glide.integration.compose.test.expectDisplayedDrawableSize\nimport com.bumptech.glide.integration.ktx.InternalGlideApi\nimport com.bumptech.glide.integration.ktx.Size\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.target.Target\nimport com.google.common.truth.Truth.assertThat\nimport java.util.concurrent.atomic.AtomicInteger\nimport java.util.concurrent.atomic.AtomicReference\nimport org.junit.Rule\nimport org.junit.Test\n\nclass GlideImageTest {\n    private val context: Context = ApplicationProvider.getApplicationContext()\n    @get:Rule val glideComposeRule = GlideComposeRule()\n\n    @Test\n    fun glideImage_noModifierSize_resourceDrawable_displaysDrawable() {\n        val description = \"test\"\n        val resourceId = android.R.drawable.star_big_on\n        glideComposeRule.setContent {\n            GlideImage(model = resourceId, contentDescription = description)\n        }\n\n        glideComposeRule.waitForIdle()\n\n        val expectedSize = resourceId.bitmapSize()\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawableSize(expectedSize))\n    }\n\n    @Test\n    fun glideImage_withSizeLargerThanImage_noTransformSet_doesNotUpscaleImage() {\n        val description = \"test\"\n        val resourceId = android.R.drawable.star_big_on\n        glideComposeRule.setContent {\n            GlideImage(\n                model = resourceId,\n                contentDescription = description,\n                modifier = Modifier.size(300.dp, 300.dp),\n            )\n        }\n\n        glideComposeRule.waitForIdle()\n\n        val expectedSize = resourceId.bitmapSize()\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawableSize(expectedSize))\n    }\n\n    @Test\n    fun glideImage_withChangingModel_refreshes() {\n        val description = \"test\"\n\n        val firstDrawable: Drawable = context.getDrawable(android.R.drawable.star_big_off)!!\n        val secondDrawable: Drawable = context.getDrawable(android.R.drawable.star_big_on)!!\n\n        glideComposeRule.setContent {\n            val model = remember { mutableStateOf(firstDrawable) }\n\n            fun swapModel() {\n                model.value = secondDrawable\n            }\n\n            Column {\n                TextButton(onClick = ::swapModel) { Text(text = \"Swap\") }\n                GlideImage(\n                    model = model.value,\n                    modifier = Modifier.size(100.dp),\n                    contentDescription = description,\n                )\n            }\n        }\n\n        glideComposeRule.waitForIdle()\n        glideComposeRule.onNodeWithText(\"Swap\").performClick()\n        glideComposeRule.waitForIdle()\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawable(secondDrawable))\n    }\n\n    @Test\n    fun glideImage_withSizeLargerThanImage_upscaleTransformSet_upscalesImage() {\n        val viewDimension = 300\n        val description = \"test\"\n        val sizeRef = AtomicReference<Size>()\n        glideComposeRule.setContent {\n            GlideImage(\n                model = android.R.drawable.star_big_on,\n                requestBuilderTransform = { it.fitCenter() },\n                contentDescription = description,\n                modifier = Modifier.size(viewDimension.dp, viewDimension.dp),\n            )\n\n            with(LocalDensity.current) {\n                val pixels = viewDimension.dp.roundToPx()\n                sizeRef.set(Size(pixels, pixels))\n            }\n        }\n\n        glideComposeRule.waitForIdle()\n\n        val pixels = sizeRef.get()\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawableSize(pixels))\n    }\n\n    @Test\n    fun glideImage_withThumbnail_prefersFullSizeImage() {\n        val description = \"test\"\n        val thumbnailDrawable = context.getDrawable(android.R.drawable.star_big_off)\n        val fullsizeDrawable = context.getDrawable(android.R.drawable.star_big_on)\n\n        glideComposeRule.setContent {\n            GlideImage(\n                model = fullsizeDrawable,\n                requestBuilderTransform = {\n                    it.thumbnail(Glide.with(context).load(thumbnailDrawable))\n                },\n                contentDescription = description,\n            )\n        }\n\n        glideComposeRule.waitForIdle()\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(expectDisplayedDrawable(fullsizeDrawable))\n    }\n\n    @Test\n    fun glideImage_withZeroSize_doesNotStartLoad() {\n        val description = \"test\"\n        glideComposeRule.setContent {\n            Box(modifier = Modifier.size(0.dp)) {\n                GlideImage(model = android.R.drawable.star_big_on, contentDescription = description)\n            }\n        }\n        glideComposeRule.waitForIdle()\n        glideComposeRule.onNodeWithContentDescription(description).assertDisplays(null)\n    }\n\n    @Test\n    fun glideImage_withNegativeSize_doesNotStartLoad() {\n        val description = \"test\"\n        glideComposeRule.setContent {\n            Box(modifier = Modifier.size((-10).dp)) {\n                GlideImage(model = android.R.drawable.star_big_on, contentDescription = description)\n            }\n        }\n        glideComposeRule.waitForIdle()\n        glideComposeRule.onNodeWithContentDescription(description).assertDisplays(null)\n    }\n\n    @Test\n    fun glideImage_withZeroWidth_validHeight_doesNotStartLoad() {\n        val description = \"test\"\n        glideComposeRule.setContent {\n            Box(modifier = Modifier.size(0.dp, 10.dp)) {\n                GlideImage(model = android.R.drawable.star_big_on, contentDescription = description)\n            }\n        }\n        glideComposeRule.waitForIdle()\n        glideComposeRule.onNodeWithContentDescription(description).assertDisplays(null)\n    }\n\n    @Test\n    fun glideImage_withValidWidth_zeroHeight_doesNotStartLoad() {\n        val description = \"test\"\n        glideComposeRule.setContent {\n            Box(modifier = Modifier.size(10.dp, 0.dp)) {\n                GlideImage(model = android.R.drawable.star_big_on, contentDescription = description)\n            }\n        }\n        glideComposeRule.waitForIdle()\n        glideComposeRule.onNodeWithContentDescription(description).assertDisplays(null)\n    }\n\n    @Test\n    fun glideImage_withZeroSize_thenValidSize_startsLoadWithValidSize() {\n        val description = \"test\"\n        val resourceId = android.R.drawable.star_big_on\n        val validSizeDp = 10\n        glideComposeRule.setContent {\n            val currentSize = remember { mutableStateOf(0.dp) }\n            fun swapSize() {\n                currentSize.value = validSizeDp.dp\n            }\n\n            TextButton(onClick = ::swapSize) { Text(text = \"Swap\") }\n            Box(modifier = Modifier.size(currentSize.value)) {\n                GlideImage(model = resourceId, contentDescription = description)\n            }\n        }\n        glideComposeRule.waitForIdle()\n        glideComposeRule.onNodeWithText(\"Swap\").performClick()\n        glideComposeRule.waitForIdle()\n\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(\n                expectDisplayedDrawableSize(\n                    Size(validSizeDp.dpToPixels(), validSizeDp.dpToPixels())\n                )\n            )\n    }\n\n    @Test\n    fun glideImage_withZeroSize_thenMultipleValidSizes_startsLoadWithFirstValidSize() {\n        val description = \"test\"\n        val resourceId = android.R.drawable.star_big_on\n        val validSizeDps = listOf(10, 20, 30, 40)\n        glideComposeRule.setContent {\n            val currentSize = remember { mutableStateOf(0.dp) }\n            val currentSizeIndex = remember { mutableStateOf(0) }\n            fun swapSize() {\n                currentSize.value = validSizeDps[currentSizeIndex.value].dp\n                currentSizeIndex.value++\n            }\n\n            TextButton(onClick = ::swapSize) { Text(text = \"Swap\") }\n            Box(modifier = Modifier.size(currentSize.value)) {\n                GlideImage(model = resourceId, contentDescription = description)\n            }\n        }\n        repeat(validSizeDps.size) {\n            glideComposeRule.waitForIdle()\n            glideComposeRule.onNodeWithText(\"Swap\").performClick()\n        }\n        glideComposeRule.waitForIdle()\n\n        val expectedSize = validSizeDps[0]\n        glideComposeRule\n            .onNodeWithContentDescription(description)\n            .assert(\n                expectDisplayedDrawableSize(\n                    Size(expectedSize.dpToPixels(), expectedSize.dpToPixels())\n                )\n            )\n    }\n\n    @Test\n    fun glideImage_withSuccessfulResource_callsOnResourceReadyOnce() {\n        val onResourceReadyCounter = AtomicInteger()\n        val requestListener =\n            object : RequestListener<Drawable> {\n                override fun onLoadFailed(\n                    e: GlideException?,\n                    model: Any?,\n                    target: Target<Drawable>?,\n                    isFirstResource: Boolean,\n                ): Boolean {\n                    throw UnsupportedOperationException()\n                }\n\n                override fun onResourceReady(\n                    resource: Drawable?,\n                    model: Any?,\n                    target: Target<Drawable>?,\n                    dataSource: DataSource?,\n                    isFirstResource: Boolean,\n                ): Boolean {\n                    onResourceReadyCounter.incrementAndGet()\n                    return false\n                }\n            }\n\n        glideComposeRule.setContent {\n            GlideImage(model = android.R.drawable.star_big_on, contentDescription = \"\") {\n                it.listener(requestListener)\n            }\n        }\n\n        glideComposeRule.waitForIdle()\n\n        assertThat(onResourceReadyCounter.get()).isEqualTo(1)\n    }\n}\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/RememberGlidePreloadingDataTest.kt",
    "content": "@file:OptIn(ExperimentalGlideComposeApi::class, ExperimentalGlideComposeApi::class)\n\npackage com.bumptech.glide.integration.compose\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.DrawableRes\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.material.Text\nimport androidx.compose.material.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.platform.testTag\nimport androidx.compose.ui.test.hasTestTag\nimport androidx.compose.ui.test.onNodeWithText\nimport androidx.compose.ui.test.performClick\nimport androidx.compose.ui.test.performScrollToIndex\nimport androidx.compose.ui.unit.dp\nimport androidx.test.core.app.ApplicationProvider\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.integration.compose.test.GlideComposeRule\nimport com.bumptech.glide.request.target.Target\nimport com.google.common.truth.Truth.assertThat\nimport org.junit.Rule\nimport org.junit.Test\n\nclass RememberGlidePreloadingDataTest {\n    private val context: Context = ApplicationProvider.getApplicationContext()\n    @get:Rule val glideComposeRule = GlideComposeRule()\n\n    @Test\n    fun rememberGlidePreloadingData_withoutScroll_preloadsNextItem() {\n        glideComposeRule.setContent {\n            val preloadingData = rememberOneItemAtATimePreloadingData()\n\n            LazyRow(modifier = Modifier.testTag(listTestTag)) {\n                items(preloadingData.size) { index ->\n                    preloadingData.triggerPreload(index)\n                    GlideImage(\n                        model = model,\n                        contentDescription = imageContentDescription(index),\n                        Modifier.fillParentMaxWidth(),\n                    )\n                }\n            }\n        }\n\n        assertThatModelIsInMemoryCache(preloadModels[1])\n    }\n\n    @Test\n    fun glideLazyListPreloader_onScroll_preloadsAheadInDirectionOfScroll() {\n        glideComposeRule.setContent {\n            val preloadingData = rememberOneItemAtATimePreloadingData()\n            LazyRow(modifier = Modifier.testTag(listTestTag)) {\n                items(preloadingData.size) { index ->\n                    preloadingData.triggerPreload(index)\n                    GlideImage(\n                        model = model,\n                        contentDescription = imageContentDescription(index),\n                        Modifier.fillParentMaxWidth(),\n                    )\n                }\n            }\n        }\n\n        val scrollToIndex = 1\n        glideComposeRule.onNode(hasTestTag(listTestTag)).performScrollToIndex(scrollToIndex)\n\n        assertThatModelIsInMemoryCache(preloadModels[2])\n    }\n\n    @Test\n    fun glideLazyListPreloader_withHeaderItem_onScroll_doesNotCrash() {\n        glideComposeRule.setContent {\n            val preloadingData = rememberOneItemAtATimePreloadingData()\n\n            LazyRow(modifier = Modifier.testTag(listTestTag)) {\n                item { Text(text = \"Header\") }\n                items(preloadingData.size) { index ->\n                    preloadingData.triggerPreload(index)\n                    GlideImage(\n                        model = model,\n                        contentDescription = imageContentDescription(index),\n                        Modifier.fillParentMaxWidth(),\n                    )\n                }\n            }\n        }\n\n        // Scroll to the 0th image, accounting for the first header item.\n        val scrollToIndex = 1\n        glideComposeRule.onNode(hasTestTag(listTestTag)).performScrollToIndex(scrollToIndex)\n        // Make sure the next image, the 1th, is in memory due to preloading.\n        assertThatModelIsInMemoryCache(preloadModels[1])\n    }\n\n    @Test\n    fun glideLazyListPreloader_whenDataChanges_onScroll_preloadsUpdatedData() {\n        glideComposeRule.setContent {\n            // Swap both to avoid confusing the preloader. The preloader doesn't notice or take into\n            // account data set changes (this is a bug in the Java preloading API)...\n            val currentPreloadModels = remember { mutableStateListOf<Int>() }\n            val currentModels = remember { mutableStateListOf<Int>() }\n            // Use a button to swap data because we can't mutate state in setContent easily from\n            // outside\n            // the method, nor can you call setContent multiple times.\n            fun swapData() {\n                currentPreloadModels.addAll(preloadModels)\n                currentModels.addAll(preloadModels)\n            }\n            val preloadData =\n                rememberGlidePreloadingData(\n                    data = currentPreloadModels,\n                    preloadImageSize = Target.SIZE_ORIGINAL.toSize(),\n                    numberOfItemsToPreload = 1,\n                    fixedVisibleItemCount = 1,\n                ) { data: Int, requestBuilder: RequestBuilder<Drawable> ->\n                    requestBuilder.load(data).removeTheme()\n                }\n\n            TextButton(onClick = ::swapData) { Text(text = \"Swap\") }\n\n            Column {\n                LazyRow(\n                    modifier = Modifier.testTag(listTestTag),\n                    horizontalArrangement = Arrangement.spacedBy(10.dp),\n                ) {\n                    items(currentModels.size) { index ->\n                        // This mismatch between currentModels and preloadData may lead to errors in\n                        // the future\n                        // because items may be recomposed before the setContent method's function\n                        // is\n                        // recomposed. See https://chat.google.com/room/AAAAYRnp4-Y/AvFrBgb_peU for\n                        // a bunch of\n                        // detailed discussion.\n                        preloadData.triggerPreload(index)\n                        GlideImage(\n                            model = currentModels[index],\n                            contentDescription = imageContentDescription(index),\n                            Modifier.fillParentMaxWidth(),\n                        )\n                    }\n                }\n            }\n        }\n\n        glideComposeRule.onNodeWithText(\"Swap\").performClick()\n        glideComposeRule.waitForIdle()\n        val scrollToIndex = 1\n        glideComposeRule.onNode(hasTestTag(listTestTag)).performScrollToIndex(scrollToIndex)\n\n        assertThatModelIsInMemoryCache(preloadModels[scrollToIndex + 1])\n    }\n\n    @Test\n    fun glideLazyListPreloader_withHeaderItems_andPositionFunction_onScroll_preloadsTheFirstItem() {\n        val numHeaderItems = 3\n        glideComposeRule.setContent {\n            val data = rememberOneItemAtATimePreloadingData()\n            LazyRow(modifier = Modifier.testTag(listTestTag)) {\n                repeat(numHeaderItems) { item { Text(text = \"Header$it\") } }\n                items(data.size) { index ->\n                    data.triggerPreload(index)\n                    GlideImage(\n                        model = model,\n                        contentDescription = imageContentDescription(index),\n                        Modifier.fillParentMaxWidth(),\n                    )\n                }\n            }\n        }\n\n        val imageIndex = 1\n        val scrollToIndex = numHeaderItems + imageIndex\n        glideComposeRule.onNode(hasTestTag(listTestTag)).performScrollToIndex(scrollToIndex)\n\n        assertThatModelIsInMemoryCache(preloadModels[imageIndex + 1])\n    }\n\n    // Ignore the preload request because we want to test that the preloader loaded a model\n    // and not be confused by our UI loading a model. Do not ignore the preload request\n    // builder in real code!\n    @Composable\n    private fun <DataT> GlidePreloadingData<DataT>.triggerPreload(index: Int) = this[index].first\n\n    @Composable\n    private fun rememberOneItemAtATimePreloadingData(): GlidePreloadingData<Int> {\n        return rememberGlidePreloadingData(\n            data = preloadModels,\n            preloadImageSize = Target.SIZE_ORIGINAL.toSize(),\n            numberOfItemsToPreload = 1,\n            fixedVisibleItemCount = 1,\n        ) { model, requestBuilder ->\n            requestBuilder.load(model).removeTheme()\n        }\n    }\n\n    private fun assertThatModelIsInMemoryCache(@DrawableRes model: Int) {\n        // Wait for previous async image loads to finish\n        glideComposeRule.waitForIdle()\n        val nextPreloadModel: Drawable =\n            Glide.with(context).load(model).removeTheme().onlyRetrieveFromCache(true).submit().get()\n        assertThat(nextPreloadModel).isNotNull()\n    }\n\n    // We're loading the same resource across two different Contexts. One is the Context from the\n    // instrumentation package, the other is the package under test. Each Context has it's own\n    // Theme,\n    // neither of which are equal to each other. So that we can verify an item is loaded into\n    // memory,\n    // we remove the themes from all requests that we need to have matching cache keys.\n    private fun <T> RequestBuilder<T>.removeTheme() = theme(null)\n\n    private companion object {\n        const val model = android.R.drawable.star_big_on\n\n        // Use different preload and non-preload models so that we can assert on which items are\n        // preloaded and not loaded by the list. This is bad practice in production code and would\n        // waste\n        // resources while doing nothing useful in a real app.\n        val preloadModels =\n            listOf(\n                android.R.drawable.btn_minus,\n                android.R.drawable.btn_radio,\n                android.R.drawable.btn_star,\n            )\n\n        const val listTestTag = \"listTestTag\"\n\n        fun imageContentDescription(index: Int) = \"Image $index\"\n    }\n}\n\nprivate fun Int.toSize() = this.toFloat().let { Size(it, it) }\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/test/GlideComposeRule.kt",
    "content": "package com.bumptech.glide.integration.compose.test\n\nimport androidx.compose.ui.test.junit4.ComposeContentTestRule\nimport androidx.compose.ui.test.junit4.createComposeRule\nimport com.bumptech.glide.load.engine.executor.GlideIdlingResourceInit\nimport com.bumptech.glide.testutil.TearDownGlide\nimport org.junit.rules.RuleChain\nimport org.junit.rules.TestRule\nimport org.junit.runner.Description\nimport org.junit.runners.model.Statement\n\n/**\n * Merges [TearDownGlide], [ComposeContentTestRule] and [GlideIdlingResourceInit] into a single\n * helper rule that's common across (most of) Glide's compose integration tests.\n */\nclass GlideComposeRule(private val composeRule: ComposeContentTestRule = createComposeRule()) :\n    TestRule, ComposeContentTestRule by composeRule {\n    private val rules = RuleChain.outerRule(TearDownGlide()).around(composeRule)\n\n    override fun apply(base: Statement?, description: Description?): Statement {\n        return rules.apply(\n            object : Statement() {\n                override fun evaluate() {\n                    GlideIdlingResourceInit.initGlide(this@GlideComposeRule)\n                    base?.evaluate()\n                }\n            },\n            description,\n        )\n    }\n}\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/test/expectations.kt",
    "content": "@file:OptIn(InternalGlideApi::class)\n\npackage com.bumptech.glide.integration.compose.test\n\nimport android.content.Context\nimport android.content.res.Resources\nimport android.graphics.Bitmap\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.Drawable\nimport android.util.TypedValue\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.ui.semantics.SemanticsPropertyKey\nimport androidx.compose.ui.test.SemanticsMatcher\nimport androidx.test.core.app.ApplicationProvider\nimport com.bumptech.glide.integration.compose.DisplayedDrawableKey\nimport com.bumptech.glide.integration.ktx.InternalGlideApi\nimport com.bumptech.glide.integration.ktx.Size\nimport kotlin.math.roundToInt\n\nprivate fun context(): Context = ApplicationProvider.getApplicationContext()\n\nfun Int.dpToPixels() =\n    TypedValue.applyDimension(\n            TypedValue.COMPLEX_UNIT_DIP,\n            this.toFloat(),\n            Resources.getSystem().displayMetrics,\n        )\n        .roundToInt()\n\nfun Int.bitmapSize() = context().resources.getDrawable(this, context().theme).size()\n\nfun Drawable.size() = (this as BitmapDrawable).bitmap.let { Size(it.width, it.height) }\n\nfun expectDisplayedResource(resourceId: Int) =\n    expectDisplayedDrawable(context().getDrawable(resourceId))\n\nfun Drawable?.bitmapOrThrow(): Bitmap? = if (this == null) null else (this as BitmapDrawable).bitmap\n\nfun expectDisplayedDrawableSize(expectedSize: Size): SemanticsMatcher =\n    expectDisplayedDrawable(expectedSize) { it?.size() }\n\nfun expectDisplayedDrawable(expectedValue: Drawable?): SemanticsMatcher =\n    expectDisplayedDrawable(expectedValue.bitmapOrThrow(), ::compareBitmaps) { it.bitmapOrThrow() }\n\nfun expectNoDrawable(): SemanticsMatcher = expectDisplayedDrawable(null)\n\nprivate fun compareBitmaps(first: Bitmap?, second: Bitmap?): Boolean {\n    if (first == null && second == null) {\n        return true\n    }\n    if (first == null || second == null) {\n        return false\n    }\n    return first.sameAs(second)\n}\n\nprivate fun <ValueT> expectDisplayedDrawable(\n    expectedValue: ValueT,\n    compare: (ValueT?, ValueT?) -> Boolean = { first, second -> first == second },\n    transform: (Drawable?) -> ValueT,\n): SemanticsMatcher =\n    expectStateValue(DisplayedDrawableKey, expectedValue, compare) { transform(it) }\n\nprivate fun <ValueT, TransformedValueT> expectStateValue(\n    key: SemanticsPropertyKey<MutableState<ValueT?>>,\n    expectedValue: TransformedValueT,\n    compare: (TransformedValueT?, TransformedValueT?) -> Boolean,\n    transform: (ValueT?) -> TransformedValueT?,\n): SemanticsMatcher =\n    SemanticsMatcher(\"${key.name} = '$expectedValue'\") {\n        val value = transform(it.config.getOrElseNullable(key) { null }?.value)\n        if (!compare(value, expectedValue)) {\n            throw AssertionError(\"Expected: $expectedValue, but was: $value\")\n        }\n        true\n    }\n\nfun expectSameInstance(expectedDrawable: Drawable) =\n    SemanticsMatcher(\"${DisplayedDrawableKey.name} = '$expectedDrawable'\") {\n        val actualValue: Drawable? =\n            it.config.getOrElseNullable(DisplayedDrawableKey) { null }?.value\n        if (actualValue !== expectedDrawable) {\n            throw AssertionError(\"Expected: $expectedDrawable, but was: $actualValue\")\n        }\n        true\n    }\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/integration/compose/test/nodes.kt",
    "content": "package com.bumptech.glide.integration.compose.test\n\nimport android.app.Application\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.DrawableRes\nimport androidx.compose.ui.test.SemanticsNodeInteraction\nimport androidx.compose.ui.test.assert\nimport androidx.compose.ui.test.junit4.ComposeContentTestRule\nimport androidx.compose.ui.test.onNodeWithContentDescription\nimport androidx.test.core.app.ApplicationProvider\n\nobject Constants {\n    const val DEFAULT_DESCRIPTION = \"test\"\n}\n\nfun ComposeContentTestRule.onNodeWithDefaultContentDescription() =\n    onNodeWithContentDescription(Constants.DEFAULT_DESCRIPTION)\n\nfun SemanticsNodeInteraction.assertDisplays(@DrawableRes resourceId: Int) =\n    assertDisplays(ApplicationProvider.getApplicationContext<Application>().getDrawable(resourceId))\n\nfun SemanticsNodeInteraction.assertDisplays(drawable: Drawable?) =\n    assert(expectDisplayedDrawable(drawable))\n\nfun SemanticsNodeInteraction.assertDisplaysInstance(drawable: Drawable) =\n    assert(expectSameInstance(drawable))\n"
  },
  {
    "path": "integration/compose/src/androidTest/java/com/bumptech/glide/load/engine/executor/GlideIdlingResourceInit.kt",
    "content": "package com.bumptech.glide.load.engine.executor\n\nimport androidx.compose.ui.test.IdlingResource\nimport androidx.compose.ui.test.junit4.ComposeTestRule\nimport androidx.test.core.app.ApplicationProvider\nimport androidx.test.espresso.idling.concurrent.IdlingThreadPoolExecutor\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.GlideBuilder\nimport java.util.concurrent.LinkedBlockingQueue\nimport java.util.concurrent.TimeUnit\n\nobject GlideIdlingResourceInit {\n\n    fun initGlide(composeRule: ComposeTestRule) {\n        val executor =\n            IdlingThreadPoolExecutor(\n                \"glide_test_thread\",\n                /* corePoolSize = */ 1,\n                /* maximumPoolSize = */ 1,\n                /* keepAliveTime = */ 1,\n                TimeUnit.SECONDS,\n                LinkedBlockingQueue(),\n            ) {\n                Thread(it)\n            }\n        composeRule.registerIdlingResource(\n            object : IdlingResource {\n                override val isIdleNow: Boolean\n                    get() = executor.isIdleNow\n            }\n        )\n        val glideExecutor = GlideExecutor(executor)\n        Glide.init(\n            ApplicationProvider.getApplicationContext(),\n            GlideBuilder()\n                .setSourceExecutor(glideExecutor)\n                .setAnimationExecutor(glideExecutor)\n                .setDiskCacheExecutor(glideExecutor),\n        )\n    }\n}\n"
  },
  {
    "path": "integration/compose/src/main/java/com/bumptech/glide/integration/compose/ExperimentalGlideComposeApi.kt",
    "content": "package com.bumptech.glide.integration.compose\n\n@RequiresOptIn(\n    level = RequiresOptIn.Level.ERROR,\n    message =\n        \"Glide's Compose integration is experimental. APIs may change or be removed without\" +\n            \" warning.\",\n)\n@Retention(AnnotationRetention.BINARY)\n@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)\npublic annotation class ExperimentalGlideComposeApi\n"
  },
  {
    "path": "integration/compose/src/main/java/com/bumptech/glide/integration/compose/GlideImage.kt",
    "content": "package com.bumptech.glide.integration.compose\n\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.DrawableRes\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.graphics.DefaultAlpha\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.layout.layout\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalInspectionMode\nimport androidx.compose.ui.semantics.SemanticsPropertyKey\nimport androidx.compose.ui.semantics.SemanticsPropertyReceiver\nimport androidx.compose.ui.semantics.semantics\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.RequestManager\nimport com.bumptech.glide.integration.ktx.AsyncGlideSize\nimport com.bumptech.glide.integration.ktx.ExperimentGlideFlows\nimport com.bumptech.glide.integration.ktx.ImmediateGlideSize\nimport com.bumptech.glide.integration.ktx.InternalGlideApi\nimport com.bumptech.glide.integration.ktx.ResolvableGlideSize\nimport com.bumptech.glide.integration.ktx.Size\nimport com.bumptech.glide.integration.ktx.Status\nimport com.google.accompanist.drawablepainter.rememberDrawablePainter\n\n/** Mutates and returns the given [RequestBuilder] to apply relevant options. */\npublic typealias RequestBuilderTransform<T> = (RequestBuilder<T>) -> RequestBuilder<T>\n\n/**\n * Start a request by passing [model] to [RequestBuilder.load] using the given [requestManager] and\n * then applying the [requestBuilderTransform] function to add options or apply mutations if the\n * caller desires.\n *\n * [alignment], [contentScale], [alpha], [colorFilter] and [contentDescription] have the same\n * defaults (if any) and function identically to the parameters in [Image].\n *\n * If you want to restrict the size of this [Composable], use the given [modifier]. If you'd like to\n * force the size of the pixels you load to be different than the display area, use\n * [RequestBuilder.override]. Often you can get better performance by setting an explicit size so\n * that we do not have to wait for layout to fetch the image. If the size set via the [modifier] is\n * dependent on the content, Glide will probably end up loading the image using\n * [com.bumptech.glide.request.target.Target.SIZE_ORIGINAL]. Avoid `SIZE_ORIGINAL`, implicitly or\n * explicitly if you can. You may end up loading a substantially larger image than you need, which\n * will increase memory usage and may also increase latency.\n *\n * If you provide your own [requestManager] rather than using this method's default, consider using\n * [remember] at a higher level to avoid some amount of overhead of retrieving it each\n * re-composition.\n *\n * This method will inspect [contentScale] and apply a matching transformation if one exists. Any\n * automatically applied transformation can be overridden using [requestBuilderTransform]. Either\n * apply a specific transformation instead, or use [RequestBuilder.dontTransform]]\n *\n * Transitions set via [RequestBuilder.transition] are currently ignored.\n *\n * Note - this method is likely to change while we work on improving the API. Transitions are one\n * significant unexplored area. It's also possible we'll try and remove the [RequestBuilder] from\n * the direct API and instead allow all options to be set directly in the method.\n *\n * [requestBuilderTransform] is overridden by any overlapping parameter defined in this method if\n * that parameter is non-null. For example, [loading] and [failure], if non-null will be used in\n * place of any placeholder set by [requestBuilderTransform] using [RequestBuilder.placeholder] or\n * [RequestBuilder.error].\n *\n * @param loading A [Placeholder] that will be displayed while the request is loading. Specifically\n *   it's used if the request is cleared ([com.bumptech.glide.request.target.Target.onLoadCleared])\n *   or loading ([com.bumptech.glide.request.target.Target.onLoadStarted]. There's a subtle\n *   difference in behavior depending on which type of [Placeholder] you use. The resource and\n *   `Drawable` variants will be displayed if the request fails and no other failure handling is\n *   specified, but the `Composable` will not.\n * @param failure A [Placeholder] that will be displayed if the request fails. Specifically it's\n *   used when [com.bumptech.glide.request.target.Target.onLoadFailed] is called. If\n *   [RequestBuilder.error] is called in [requestBuilderTransform] with a valid [RequestBuilder] (as\n *   opposed to resource id or [Drawable]), this [Placeholder] will not be used unless the `error`\n *   [RequestBuilder] also fails. This parameter does not override error [RequestBuilder]s, only\n *   error resource ids and/or [Drawable]s.\n */\n// TODO(judds): the API here is not particularly composeesque, we should consider alternatives\n// to RequestBuilder (though thumbnail() may make that a challenge).\n// TODO(judds): Consider how to deal with transitions.\n@ExperimentalGlideComposeApi\n@OptIn(InternalGlideApi::class)\n@Composable\npublic fun GlideImage(\n    model: Any?,\n    contentDescription: String?,\n    modifier: Modifier = Modifier,\n    alignment: Alignment = Alignment.Center,\n    contentScale: ContentScale = ContentScale.Fit,\n    alpha: Float = DefaultAlpha,\n    colorFilter: ColorFilter? = null,\n    // TODO(judds): Consider using separate GlideImage* methods instead of sealed classes.\n    // See http://shortn/_x79pjkMZIH for an internal discussion.\n    loading: Placeholder? = null,\n    failure: Placeholder? = null,\n    // TODO(judds): Consider defaulting to load the model here instead of always doing so below.\n    requestBuilderTransform: RequestBuilderTransform<Drawable> = { it },\n) {\n    val requestManager: RequestManager =\n        LocalContext.current.let { remember(it) { Glide.with(it) } }\n    val requestBuilder =\n        rememberRequestBuilderWithDefaults(\n                model,\n                requestManager,\n                requestBuilderTransform,\n                contentScale,\n            )\n            .let { loading?.apply(it::placeholder, it::placeholder) ?: it }\n            .let { failure?.apply(it::error, it::error) ?: it }\n\n    val overrideSize: Size? = requestBuilder.overrideSize()\n    val (size, finalModifier) = rememberSizeAndModifier(overrideSize, modifier)\n\n    // TODO(judds): It seems like we should be able to use the production paths for\n    // resource / drawables as well as Composables. It's not totally clear what part of the prod\n    // code\n    // isn't supported.\n    if (LocalInspectionMode.current && loading?.isResourceOrDrawable() == true) {\n        PreviewResourceOrDrawable(loading, contentDescription, modifier)\n        return\n    }\n\n    SizedGlideImage(\n        requestBuilder = requestBuilder,\n        size = size,\n        modifier = finalModifier,\n        contentDescription = contentDescription,\n        alignment = alignment,\n        contentScale = contentScale,\n        alpha = alpha,\n        colorFilter = colorFilter,\n        placeholder = loading?.maybeComposable(),\n        failure = failure?.maybeComposable(),\n    )\n}\n\n@OptIn(ExperimentalGlideComposeApi::class)\n@Composable\nprivate fun PreviewResourceOrDrawable(\n    loading: Placeholder,\n    contentDescription: String?,\n    modifier: Modifier,\n) {\n    val drawable =\n        when (loading) {\n            is Placeholder.OfDrawable -> loading.drawable\n            is Placeholder.OfResourceId -> LocalContext.current.getDrawable(loading.resourceId)\n            is Placeholder.OfComposable ->\n                throw IllegalArgumentException(\n                    \"Composables should go through the production codepath\"\n                )\n        }\n    Image(\n        painter = rememberDrawablePainter(drawable),\n        modifier = modifier,\n        contentDescription = contentDescription,\n    )\n}\n\n/**\n * Used to specify a [Drawable] to use in conjunction with [GlideImage]'s `loading` or `failure`\n * parameters.\n *\n * Ideally [drawable] is non-null, but because [android.content.Context.getDrawable] can return\n * null, we allow it here. `placeholder(null)` has the same override behavior as if a non-null\n * `Drawable` were provided.\n */\n@ExperimentalGlideComposeApi\npublic fun placeholder(drawable: Drawable?): Placeholder = Placeholder.OfDrawable(drawable)\n\n/**\n * Used to specify a resource id to use in conjunction with [GlideImage]'s `loading` or `failure`\n * parameters.\n *\n * In addition to being slightly simpler than manually fetching a [Drawable] and passing it to\n * [placeholder], this method can be more efficient because the [Drawable] will only be loaded when\n * needed.\n */\n@ExperimentalGlideComposeApi\npublic fun placeholder(@DrawableRes resourceId: Int): Placeholder =\n    Placeholder.OfResourceId(resourceId)\n\n/**\n * Used to specify a [Composable] function to use in conjunction with [GlideImage]'s `loading` or\n * `failure` parameter.\n *\n * Providing a nested [GlideImage] is not recommended. Use [RequestBuilder.thumbnail] or\n * [RequestBuilder.error] as an alternative.\n */\n@ExperimentalGlideComposeApi\npublic fun placeholder(composable: @Composable () -> Unit): Placeholder =\n    Placeholder.OfComposable(composable)\n\n/**\n * Content to display during a particular state of a Glide Request, for example while the request is\n * loading or if the request fails.\n *\n * `of(Drawable)` and `of(resourceId)` trigger fewer recompositions than `of(@Composable () ->\n * Unit)` so you should only use the Composable variant if you require something more complex than a\n * simple color or a static image.\n *\n * `of(@Composable () -> Unit)` will display the [Composable] inside a [Box] whose modifier is the\n * one provided to [GlideImage]. Doing so allows Glide to infer the requested size if one is not\n * explicitly specified on the request itself.\n */\n@ExperimentalGlideComposeApi\npublic sealed class Placeholder {\n    internal class OfDrawable(internal val drawable: Drawable?) : Placeholder()\n\n    internal class OfResourceId(@DrawableRes internal val resourceId: Int) : Placeholder()\n\n    internal class OfComposable(internal val composable: @Composable () -> Unit) : Placeholder()\n\n    internal fun isResourceOrDrawable() =\n        when (this) {\n            is OfDrawable -> true\n            is OfResourceId -> true\n            is OfComposable -> false\n        }\n\n    internal fun maybeComposable(): (@Composable () -> Unit)? =\n        when (this) {\n            is OfComposable -> this.composable\n            else -> null\n        }\n\n    internal fun <T> apply(\n        resource: (Int) -> RequestBuilder<T>,\n        drawable: (Drawable?) -> RequestBuilder<T>,\n    ): RequestBuilder<T> =\n        when (this) {\n            is OfDrawable -> drawable(this.drawable)\n            is OfResourceId -> resource(this.resourceId)\n            // Clear out any previously set placeholder.\n            else -> drawable(null)\n        }\n}\n\n@OptIn(InternalGlideApi::class)\nprivate data class SizeAndModifier(val size: ResolvableGlideSize, val modifier: Modifier)\n\n@OptIn(InternalGlideApi::class)\n@Composable\nprivate fun rememberSizeAndModifier(overrideSize: Size?, modifier: Modifier) =\n    remember(overrideSize, modifier) {\n        if (overrideSize != null) {\n            SizeAndModifier(ImmediateGlideSize(overrideSize), modifier)\n        } else {\n            val sizeObserver = SizeObserver()\n            SizeAndModifier(\n                AsyncGlideSize(sizeObserver::getSize),\n                modifier.sizeObservingModifier(sizeObserver),\n            )\n        }\n    }\n\n@Composable\nprivate fun rememberRequestBuilderWithDefaults(\n    model: Any?,\n    requestManager: RequestManager,\n    requestBuilderTransform: RequestBuilderTransform<Drawable>,\n    contentScale: ContentScale,\n) =\n    remember(model, requestManager, requestBuilderTransform, contentScale) {\n        requestBuilderTransform(requestManager.load(model).contentScaleTransform(contentScale))\n    }\n\nprivate fun RequestBuilder<Drawable>.contentScaleTransform(\n    contentScale: ContentScale\n): RequestBuilder<Drawable> {\n    return when (contentScale) {\n        ContentScale.Crop -> {\n            optionalCenterCrop()\n        }\n        ContentScale.Inside,\n        ContentScale.Fit -> {\n            // Outside compose, glide would use fitCenter() for FIT. But that's probably not a good\n            // decision given how unimportant Bitmap re-use is relative to minimizing texture sizes\n            // now.\n            // So instead we'll do something different and prefer not to upscale, which means using\n            // centerInside(). The UI can still scale the view even if the Bitmap is smaller.\n            optionalCenterInside()\n        }\n        else -> {\n            this\n        }\n    }\n    // TODO(judds): Think about how to handle the various fills\n}\n\n@OptIn(InternalGlideApi::class, ExperimentGlideFlows::class)\n@Composable\nprivate fun SizedGlideImage(\n    requestBuilder: RequestBuilder<Drawable>,\n    size: ResolvableGlideSize,\n    modifier: Modifier,\n    contentDescription: String?,\n    alignment: Alignment,\n    contentScale: ContentScale,\n    alpha: Float,\n    colorFilter: ColorFilter?,\n    placeholder: @Composable (() -> Unit)?,\n    failure: @Composable (() -> Unit)?,\n) {\n    // Use a Box so we can infer the size if the request doesn't have an explicit size.\n    @Composable fun @Composable () -> Unit.boxed() = Box(modifier = modifier) { this@boxed() }\n\n    val painter = rememberGlidePainter(requestBuilder = requestBuilder, size = size)\n    if (placeholder != null && painter.status.showPlaceholder()) {\n        placeholder.boxed()\n    } else if (failure != null && painter.status == Status.FAILED) {\n        failure.boxed()\n    } else {\n        Image(\n            painter = painter,\n            contentDescription = contentDescription,\n            alignment = alignment,\n            contentScale = contentScale,\n            alpha = alpha,\n            colorFilter = colorFilter,\n            modifier =\n                modifier.then(Modifier.semantics { displayedDrawable = painter.currentDrawable }),\n        )\n    }\n}\n\n@OptIn(ExperimentGlideFlows::class)\nprivate fun Status.showPlaceholder(): Boolean =\n    when (this) {\n        Status.RUNNING -> true\n        Status.CLEARED -> true\n        else -> false\n    }\n\n@OptIn(InternalGlideApi::class)\n@Composable\nprivate fun rememberGlidePainter(\n    requestBuilder: RequestBuilder<Drawable>,\n    size: ResolvableGlideSize,\n): GlidePainter {\n    val scope = rememberCoroutineScope()\n    val lifecycleOwner = LocalLifecycleOwner.current\n    // TODO(judds): Calling onRemembered here manually might make a minor improvement in how quickly\n    //  the image load is started, but it also triggers a recomposition. I can't figure out why it\n    //  triggers a recomposition\n    return remember(requestBuilder, size, lifecycleOwner) {\n        GlidePainter(requestBuilder, size, scope, lifecycleOwner)\n    }\n}\n\n@OptIn(InternalGlideApi::class)\nprivate fun Modifier.sizeObservingModifier(sizeObserver: SizeObserver): Modifier =\n    this.layout { measurable, constraints ->\n        val inferredSize = constraints.inferredGlideSize()\n        if (inferredSize != null) {\n            sizeObserver.setSize(inferredSize)\n        }\n        val placeable = measurable.measure(constraints)\n        layout(placeable.width, placeable.height) { placeable.place(0, 0) }\n    }\n\ninternal val DisplayedDrawableKey =\n    SemanticsPropertyKey<MutableState<Drawable?>>(\"DisplayedDrawable\")\ninternal var SemanticsPropertyReceiver.displayedDrawable by DisplayedDrawableKey\n"
  },
  {
    "path": "integration/compose/src/main/java/com/bumptech/glide/integration/compose/GlidePainter.kt",
    "content": "package com.bumptech.glide.integration.compose\n\nimport android.graphics.drawable.Animatable\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.RememberObserver\nimport androidx.compose.runtime.Stable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.graphics.DefaultAlpha\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.graphics.drawscope.DrawScope\nimport androidx.compose.ui.graphics.painter.BitmapPainter\nimport androidx.compose.ui.graphics.painter.ColorPainter\nimport androidx.compose.ui.graphics.painter.Painter\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.integration.ktx.ExperimentGlideFlows\nimport com.bumptech.glide.integration.ktx.InternalGlideApi\nimport com.bumptech.glide.integration.ktx.Placeholder\nimport com.bumptech.glide.integration.ktx.ResolvableGlideSize\nimport com.bumptech.glide.integration.ktx.Resource\nimport com.bumptech.glide.integration.ktx.Status\nimport com.bumptech.glide.integration.ktx.flowResolvable\nimport com.google.accompanist.drawablepainter.DrawablePainter\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.job\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\n\n// This class is inspired by a similar implementation in the excellent Coil library\n// (https://github.com/coil-kt/coil), specifically:\n// https://github.com/coil-kt/coil/blob/main/coil-compose-base/src/main/java/coil/compose/AsyncImagePainter.kt\n@Stable\ninternal class GlidePainter\n@OptIn(InternalGlideApi::class)\nconstructor(\n    private val requestBuilder: RequestBuilder<Drawable>,\n    private val size: ResolvableGlideSize,\n    scope: CoroutineScope,\n    private val lifecycleOwner: LifecycleOwner,\n) : Painter(), RememberObserver {\n    @OptIn(ExperimentGlideFlows::class)\n    internal var status: Status by mutableStateOf(Status.CLEARED)\n    internal val currentDrawable: MutableState<Drawable?> = mutableStateOf(null)\n    private var alpha: Float by mutableStateOf(DefaultAlpha)\n    private var colorFilter: ColorFilter? by mutableStateOf(null)\n    private var delegate: Painter? by mutableStateOf(null)\n    private val scope =\n        scope + SupervisorJob(parent = scope.coroutineContext.job) + Dispatchers.Main.immediate\n    private var currentJob: Job? = null\n\n    init {\n        scope.launch {\n            // If the Lifecycle state is at least STARTED, start the animation. Otherwise, stop the\n            // animation.\n            lifecycleOwner.lifecycle.currentStateFlow.collect {\n                if (it.isAtLeast(Lifecycle.State.STARTED)) {\n                    currentDrawable.value?.let { drawable ->\n                        if (drawable is Animatable) {\n                            drawable.start()\n                        }\n                    }\n                } else {\n                    currentDrawable.value?.let { drawable ->\n                        if (drawable is Animatable) {\n                            drawable.stop()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override val intrinsicSize: Size\n        get() = delegate?.intrinsicSize ?: Size.Unspecified\n\n    override fun DrawScope.onDraw() {\n        delegate?.apply { draw(size, alpha, colorFilter) }\n    }\n\n    override fun onAbandoned() {\n        (delegate as? RememberObserver)?.onAbandoned()\n    }\n\n    override fun onForgotten() {\n        (delegate as? RememberObserver)?.onForgotten()\n        currentJob?.cancel()\n        currentJob = null\n        currentDrawable.value = null\n        delegate = null\n    }\n\n    override fun onRemembered() {\n        (delegate as? RememberObserver)?.onRemembered()\n        if (currentJob == null) {\n            currentJob = launchRequest()\n        }\n        // In case the onRemembered is called after the lifecycle onStop, it will start the\n        // animation,\n        // stop it here again.\n        if (!lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {\n            currentDrawable.value?.let { drawable ->\n                if (drawable is Animatable) {\n                    drawable.stop()\n                }\n            }\n        }\n    }\n\n    @OptIn(ExperimentGlideFlows::class, InternalGlideApi::class)\n    private fun launchRequest() =\n        this.scope.launch {\n            requestBuilder.flowResolvable(size).collect {\n                updateDelegate(\n                    when (it) {\n                        is Resource -> it.resource\n                        is Placeholder -> it.placeholder\n                    }\n                )\n                status = it.status\n            }\n        }\n\n    private fun Drawable.toPainter() =\n        when (this) {\n            is BitmapDrawable -> BitmapPainter(bitmap.asImageBitmap())\n            is ColorDrawable -> ColorPainter(Color(color))\n            else -> DrawablePainter(mutate())\n        }\n\n    private fun updateDelegate(drawable: Drawable?) {\n        val newDelegate = drawable?.toPainter()\n        val oldDelegate = delegate\n        if (newDelegate !== oldDelegate) {\n            (oldDelegate as? RememberObserver)?.onForgotten()\n            (newDelegate as? RememberObserver)?.onRemembered()\n            currentDrawable.value = drawable\n            delegate = newDelegate\n        }\n    }\n\n    override fun applyAlpha(alpha: Float): Boolean {\n        this.alpha = alpha\n        return true\n    }\n\n    override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {\n        this.colorFilter = colorFilter\n        return true\n    }\n}\n"
  },
  {
    "path": "integration/compose/src/main/java/com/bumptech/glide/integration/compose/Preload.kt",
    "content": "package com.bumptech.glide.integration.compose\n\nimport android.graphics.drawable.Drawable\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.platform.LocalContext\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.ListPreloader\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.RequestManager\n\nprivate const val DEFAULT_ITEMS_TO_PRELOAD = 10\n\n/**\n * Preloads ahead of the data access position on the returned [GlidePreloadingData], similar to\n * [ListPreloader] and [com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader].\n *\n * The only time this API is useful is when your UI also loads an item with exactly the same\n * options, model and size. You can ensure you're doing so by using the [RequestBuilder] returned by\n * [GlidePreloadingData.get]\n *\n * Typical usage will look something like this:\n * ```\n * val glidePreloadingData =\n *   rememberGlidePreloadingData(myDataList, THUMBNAIL_SIZE) { myDataItem, requestBuilder ->\n *     // THUMBNAIL_SIZE is applied for you, but .load() is not because determining the model from\n *     // the underlying data isn't trivial. Don't forget to call .load()!\n *     requestBuilder.load(myDataItem.url)\n *   }\n *\n *  LazyRow(...) {\n *    item { Text(text = \"Header\") }\n *    items(glidePreloadingData.size) { index ->\n *      val (myDataItem, preloadRequest) = glidePreloadingData[index]\n *      GlideImage(model = item.url, contentDescription = item.description, ...) { primaryRequest ->\n *        primaryRequest.thumbnail(preloadRequest)\n *      }\n *    }\n *  }\n *  ```\n *\n * Note that preloading will not occur until the first access of `glidePreloadingData`. If you have\n * multiple disjoint data sets that you'd like to preload, or have some number of preceding header\n * rows prior to your first image, you can optionally add a few manual calls to make preloading\n * continue smoothly across data sets. One way you might do so is to call the next data set toward\n * the end of the previous data set, e.g.:\n *\n *  ```\n *  val itemsToPreload = 15\n *  items(firstDataSet.size) { index ->\n *    ... // Do something with first data set.\n *\n *    // Then as you get to the end of the first data set, start preloading the next data set\n *    manually\n *    if (index >= firstDataSet.size - itemsToPreload) {\n *      nextDataSet[itemsToPreload - (firstDataSet.size - index)]\n *    }\n *  }\n *  ```\n *\n * @param dataSize The total number of items to display and preload.\n * @param dataGetter A getter for the item at the given index (ie [List.get].\n * @param preloadImageSize The override size we'll pass to [RequestBuilder.override] .\n * @param numberOfItemsToPreload The number of items to preload ahead of the user's current\n *   position. This should be tested for each application. If the total memory size of the preloaded\n *   images exceeds the memory cache size, preloading for a lazy list is not effective. However if\n *   you preload too few things, the buffer may be small enough that images are not available when\n *   they could be, so it's always a balancing act. The smaller the preloaded image, the more you\n *   can preload.\n * @param fixedVisibleItemCount The number of visible items. In some cases this can vary widely in\n *   which case you can leave this value `null`. If the number of visible items is always one or\n *   two, it might make sense to just set this to the larger of the two to reduce churn in the\n *   preloader.\n * @param requestBuilderTransform See [ListPreloader.PreloadModelProvider.getPreloadRequestBuilder].\n *   You should call [RequestBuilder.load] on the given `item` so that any type specific options\n *   applied the matching [RequestManager.load] method are applied identically to the preload\n *   request. Remember that the request produced by this transform must exactly match the request\n *   made in your non-preload request for preloading to be useful.\n */\n@Composable\npublic fun <DataT : Any> rememberGlidePreloadingData(\n    dataSize: Int,\n    dataGetter: (Int) -> DataT,\n    preloadImageSize: Size,\n    numberOfItemsToPreload: Int = DEFAULT_ITEMS_TO_PRELOAD,\n    fixedVisibleItemCount: Int? = null,\n    requestBuilderTransform: PreloadRequestBuilderTransform<DataT>,\n): GlidePreloadingData<DataT> {\n    val requestManager = LocalContext.current.let { remember(it) { Glide.with(it) } }\n    return remember(\n        requestManager,\n        dataSize,\n        dataGetter,\n        preloadImageSize,\n        numberOfItemsToPreload,\n        fixedVisibleItemCount,\n        requestBuilderTransform,\n    ) {\n        val preloaderData =\n            PreloaderData(dataSize, dataGetter, requestBuilderTransform, preloadImageSize)\n        val preloader =\n            ListPreloader<DataT>(\n                requestManager,\n                PreloadModelProvider(requestManager, preloaderData),\n                PreloadDimensionsProvider(preloaderData),\n                numberOfItemsToPreload,\n            )\n        PreloadDataImpl(\n            dataSize,\n            dataGetter,\n            requestManager,\n            preloadImageSize,\n            fixedVisibleItemCount,\n            preloader,\n            requestBuilderTransform,\n        )\n    }\n}\n\n/**\n * A helper for [rememberGlidePreloadingData] that accepts a [List]. See the more general equivalent\n * for details.\n */\n@Composable\npublic fun <DataT : Any> rememberGlidePreloadingData(\n    data: List<DataT>,\n    preloadImageSize: Size,\n    numberOfItemsToPreload: Int = DEFAULT_ITEMS_TO_PRELOAD,\n    fixedVisibleItemCount: Int? = null,\n    requestBuilderTransform: PreloadRequestBuilderTransform<DataT>,\n): GlidePreloadingData<DataT> {\n    return rememberGlidePreloadingData(\n        dataSize = data.size,\n        dataGetter = data::get,\n        preloadImageSize = preloadImageSize,\n        numberOfItemsToPreload = numberOfItemsToPreload,\n        fixedVisibleItemCount = fixedVisibleItemCount,\n        requestBuilderTransform = requestBuilderTransform,\n    )\n}\n\nprivate data class PreloaderData<DataT>(\n    val dataSize: Int,\n    val dataAccessor: (Int) -> DataT,\n    val requestBuilderTransform: PreloadRequestBuilderTransform<DataT>,\n    val size: Size,\n) {\n    fun preloadRequests(requestManager: RequestManager, item: DataT): RequestBuilder<Drawable> {\n        return requestBuilderTransform(item, requestManager.asDrawable())\n    }\n}\n\n/**\n * Wraps a set of data, triggers image preloads based on the positions provided to [get] and exposes\n * the data and the preload [RequestBuilder].\n */\npublic interface GlidePreloadingData<DataT> {\n    /** The total number of items in the data set. */\n    public val size: Int\n\n    /**\n     * Returns the [DataT] at a given index in the data and a [RequestBuilder] that will trigger a\n     * request that exactly matches the preload request for this index.\n     *\n     * The returned [RequestBuilder] should always be used to display the item at the given index.\n     * Otherwise the preload request triggered by this call is likely useless work. The\n     * [RequestBuilder] can either be used as the primary request, or more likely, passed as the\n     * [RequestBuilder.thumbnail] to a higher resolution request.\n     *\n     * This method has side affects! Calling it will trigger preloads based on the given [index].\n     * Preloading assumes sequential access in a manner that matches what the user will see. If you\n     * need to look up data at indices for other reasons, use the underlying data source directly so\n     * that you do not confuse the preloader. Only use this method when obtaining data to display to\n     * the user.\n     */\n    @Composable public operator fun get(index: Int): Pair<DataT, RequestBuilder<Drawable>>\n}\n\nprivate class PreloadDataImpl<DataT : Any>(\n    override val size: Int,\n    private val indexToData: (Int) -> DataT,\n    private val requestManager: RequestManager,\n    private val preloadImageSize: Size,\n    private val fixedVisibleItemCount: Int?,\n    private val preloader: ListPreloader<DataT>,\n    private val requestBuilderTransform: PreloadRequestBuilderTransform<DataT>,\n) : GlidePreloadingData<DataT> {\n\n    @Composable\n    override fun get(index: Int): Pair<DataT, RequestBuilder<Drawable>> {\n        val item = indexToData(index)\n        val requestBuilder =\n            requestBuilderTransform(\n                item,\n                requestManager\n                    .asDrawable()\n                    .override(preloadImageSize.width.toInt(), preloadImageSize.height.toInt()),\n            )\n\n        LaunchedEffect(preloader, preloadImageSize, requestBuilderTransform, indexToData, index) {\n            preloader.onScroll(/* absListView= */ null, index, fixedVisibleItemCount ?: 1, size)\n        }\n        return item to requestBuilder\n    }\n}\n\nprivate class PreloadDimensionsProvider<DataT : Any>(\n    private val updatedData: PreloaderData<DataT>\n) : ListPreloader.PreloadSizeProvider<DataT> {\n    override fun getPreloadSize(item: DataT, adapterPosition: Int, perItemPosition: Int): IntArray =\n        updatedData.size.toIntArray()\n}\n\nprivate fun Size.toIntArray() = intArrayOf(width.toInt(), height.toInt())\n\nprivate class PreloadModelProvider<DataT : Any>(\n    private val requestManager: RequestManager,\n    private val data: PreloaderData<DataT>,\n) : ListPreloader.PreloadModelProvider<DataT> {\n\n    override fun getPreloadItems(position: Int): MutableList<DataT> {\n        return mutableListOf(data.dataAccessor(position))\n    }\n\n    override fun getPreloadRequestBuilder(item: DataT): RequestBuilder<*> {\n        return data.preloadRequests(requestManager, item)\n    }\n}\n\n/**\n * Provides the data to load and a [RequestBuilder] to load it with.\n *\n * You must at least call [RequestBuilder.load] with the appropriate model extracted from `item` on\n * the given `requestBuilder`. You can also optionally call any other methods available on\n * `requestBuilder` to customize your load.\n */\npublic typealias PreloadRequestBuilderTransform<DataTypeT> =\n    (item: DataTypeT, requestBuilder: RequestBuilder<Drawable>) -> RequestBuilder<Drawable>\n"
  },
  {
    "path": "integration/compose/src/main/java/com/bumptech/glide/integration/compose/Sizes.kt",
    "content": "@file:OptIn(InternalGlideApi::class)\n\npackage com.bumptech.glide.integration.compose\n\nimport androidx.compose.ui.unit.Constraints\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.integration.ktx.InternalGlideApi\nimport com.bumptech.glide.integration.ktx.Size\nimport com.bumptech.glide.integration.ktx.isValidGlideDimension\nimport com.bumptech.glide.request.target.Target\nimport kotlinx.coroutines.CompletableDeferred\n\ninternal class SizeObserver {\n    private val size = CompletableDeferred<Size>()\n\n    fun setSize(size: Size) {\n        this.size.complete(size)\n    }\n\n    suspend fun getSize(): Size {\n        return size.await()\n    }\n}\n\ninternal fun RequestBuilder<out Any?>.overrideSize(): Size? =\n    if (isOverrideSizeSet()) {\n        Size(overrideWidth, overrideHeight)\n    } else {\n        null\n    }\n\ninternal fun RequestBuilder<out Any?>.isOverrideSizeSet(): Boolean =\n    overrideWidth.isValidGlideDimension() && overrideHeight.isValidGlideDimension()\n\ninternal fun Constraints.inferredGlideSize(): Size? {\n    val width = if (hasBoundedWidth) maxWidth else Target.SIZE_ORIGINAL\n    val height = if (hasBoundedHeight) maxHeight else Target.SIZE_ORIGINAL\n    if (!width.isValidGlideDimension() || !height.isValidGlideDimension()) {\n        return null\n    }\n    return Size(width, height)\n}\n"
  },
  {
    "path": "integration/compose/src/test/java/com/bumptech/glide/integration/compose/GlideImageTest.kt",
    "content": "package com.bumptech.glide.integration.compose\n\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.test.junit4.createComposeRule\nimport androidx.compose.ui.unit.dp\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@OptIn(ExperimentalGlideComposeApi::class)\n@RunWith(AndroidJUnit4::class)\nclass GlideImageTest {\n\n    @get:Rule(order = 1) val composeRule = createComposeRule()\n\n    @Test\n    fun glideImage_zeroWidthFillBounds_doesNotCrash() {\n        composeRule.setContent {\n            GlideImage(\n                model = null,\n                contentDescription = null,\n                modifier = Modifier.width(0.dp).heightIn(0.dp, 100.dp),\n                contentScale = ContentScale.FillBounds,\n                loading = placeholder(android.R.drawable.star_on),\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "integration/concurrent/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.concurrent\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    implementation(libs.guava)\n    implementation(libs.androidx.futures)\n\n    testImplementation(project(\":mocks\"))\n    testImplementation(project(\":testutil\"))\n    testImplementation(libs.androidx.test.core)\n    testImplementation(libs.truth)\n    testImplementation(libs.junit)\n    testImplementation(libs.robolectric)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/concurrent/gradle.properties",
    "content": "POM_NAME=Glide Concurrent Integration\nPOM_ARTIFACT_ID=concurrent-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library for using Glide with ListenableFutures\n"
  },
  {
    "path": "integration/concurrent/src/main/java/com/bumptech/glide/integration/concurrent/GlideFutures.java",
    "content": "package com.bumptech.glide.integration.concurrent;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.concurrent.futures.CallbackToFutureAdapter;\nimport androidx.concurrent.futures.CallbackToFutureAdapter.Completer;\nimport androidx.concurrent.futures.CallbackToFutureAdapter.Resolver;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.request.FutureTarget;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.Executors;\nimport com.google.common.base.Function;\nimport com.google.common.util.concurrent.FluentFuture;\nimport com.google.common.util.concurrent.Futures;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.util.concurrent.Executor;\n\n/** Utilities for getting ListenableFutures out of Glide. */\npublic final class GlideFutures {\n\n  /**\n   * Preloads the resource for {@code builder} and returns a {@link ListenableFuture} that can be\n   * used to monitor status.\n   *\n   * <p>Shorthand for simply calling {@link #submitAndExecute(RequestManager, RequestBuilder,\n   * ResourceConsumer, Executor)} with an empty {@code action}.\n   */\n  // Wildcard resource types can't be directly instantiated, we don't need to care about the type\n  // here.\n  @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n  public static ListenableFuture<Void> preload(\n      final RequestManager requestManager, RequestBuilder<?> builder, Executor executor) {\n    return submitAndExecute(\n        requestManager,\n        builder,\n        new ResourceConsumer() {\n          @Override\n          public void act(Object resource) {}\n        },\n        executor);\n  }\n\n  /**\n   * Acts on a resource loaded by Glide.\n   *\n   * @param <T> The type of resource (Bitmap, Drawable etc).\n   */\n  public interface ResourceConsumer<T> {\n    void act(T resource);\n  }\n\n  /**\n   * Submits the provided request, performs the provided {@code action} and returns a {@link\n   * ListenableFuture} that can be used to cancel the request or monitor its status.\n   *\n   * <p>Cancellation is best effort and may result in some resources not being returned back to\n   * Glide's pool. In particular, if the request is cancelled after the resource is loaded by Glide,\n   * but before {@code action} is run on {@code executor}, the resource will not be returned. We\n   * have the unfortunate choice between unsafely returning resources to the pool immediately when\n   * cancel is called while they may still be in use via {@link\n   * com.google.common.util.concurrent.ClosingFuture} or occasionally failing to return resources to\n   * the pool. Because failing to return resources to the pool is inefficient, but safe, that's the\n   * route we've chosen. A more sophisticated implementation may allow us to avoid the resource\n   * inefficiency.\n   *\n   * <p>If you do not need to interact with resource, use {@link #preload(RequestManager,\n   * RequestBuilder, Executor)}. {@code preload} is more efficient because it knows that the\n   * resource is never used and can always clear the resource immediately on cancellation, unlike\n   * this method.\n   *\n   * <p>An example usage:\n   *\n   * <pre>{@code\n   *   ListenableFuture<Void> future =\n   *     submitAndExecute(\n   *       requestManager,\n   *       requestBuilder,\n   *       (bitmap) -> doSomethingWithBitmap(bitmap),\n   *       backgroundExecutor);\n   * ;\n   * }</pre>\n   *\n   * @param <T> The type of resource that will be loaded (Bitmap, Drawable, etc).\n   */\n  public static <T> ListenableFuture<Void> submitAndExecute(\n      final RequestManager requestManager,\n      RequestBuilder<T> requestBuilder,\n      final ResourceConsumer<T> action,\n      Executor executor) {\n    // If the request completes normally, then the target is cleared and the resource is returned.\n    // If the request fails while loading the image, there's no need to clear.\n    // If the request fails while calling the action, the target is cleared and the resource is\n    // returned.\n    // If the request is cancelled before the resource is loaded, then the resource is returned\n    // If the request is cancelled after the resource is loaded but before the transform runs,\n    // then the resource is dropped (but not leaked)\n    // If the request is cancelled after the transform method starts, then the resource is returned.\n    return FluentFuture.from(submitInternal(requestBuilder))\n        .transform(\n            new Function<TargetAndResult<T>, Void>() {\n              @Override\n              public Void apply(TargetAndResult<T> targetAndResult) {\n                try {\n                  action.act(targetAndResult.result);\n                } finally {\n                  requestManager.clear(targetAndResult.target);\n                }\n                return null;\n              }\n            },\n            executor);\n  }\n\n  /**\n   * Convert a pending load request into a ListenableFuture.\n   *\n   * <p>Sample code:\n   *\n   * <pre>{@code\n   * ListenableFuture<File> image =\n   *     GlideFutures.submit(requestManager.asFile().load(url));\n   * }</pre>\n   *\n   * @param requestBuilder A request builder for the resource to load. It must be tied to an\n   *     application Glide instance, and must not have a listener set.\n   * @param <T> The type of resource that will be loaded (Bitmap, Drawable, etc).\n   */\n  public static <T> ListenableFuture<T> submit(final RequestBuilder<T> requestBuilder) {\n    return transformFromTargetAndResult(submitInternal(requestBuilder));\n  }\n\n  private static <T> ListenableFuture<T> transformFromTargetAndResult(\n      ListenableFuture<TargetAndResult<T>> future) {\n    return Futures.transform(\n        future,\n        new Function<TargetAndResult<T>, T>() {\n          @Override\n          public T apply(TargetAndResult<T> input) {\n            return input.result;\n          }\n        },\n        Executors.directExecutor());\n  }\n\n  private static <T> ListenableFuture<TargetAndResult<T>> submitInternal(\n      final RequestBuilder<T> requestBuilder) {\n    return CallbackToFutureAdapter.getFuture(\n        new Resolver<TargetAndResult<T>>() {\n          // Only used for toString\n          @SuppressWarnings(\"FutureReturnValueIgnored\")\n          @Override\n          public Object attachCompleter(@NonNull Completer<TargetAndResult<T>> completer) {\n            GlideLoadingListener<T> listener = new GlideLoadingListener<>(completer);\n            final FutureTarget<T> futureTarget = requestBuilder.addListener(listener).submit();\n            completer.addCancellationListener(\n                new Runnable() {\n                  @Override\n                  public void run() {\n                    futureTarget.cancel(/* mayInterruptIfRunning= */ true);\n                  }\n                },\n                MoreExecutors.directExecutor());\n            return futureTarget;\n          }\n        });\n  }\n\n  /** Listener to convert Glide load results into ListenableFutures. */\n  private static final class GlideLoadingListener<T> implements RequestListener<T> {\n\n    private final Completer<TargetAndResult<T>> completer;\n\n    GlideLoadingListener(Completer<TargetAndResult<T>> completer) {\n      this.completer = completer;\n    }\n\n    @Override\n    public boolean onLoadFailed(\n        @Nullable GlideException e, Object model, @NonNull Target<T> target, boolean isFirst) {\n      completer.setException(e != null ? e : new RuntimeException(\"Unknown error\"));\n      return true;\n    }\n\n    @Override\n    public boolean onResourceReady(\n        @NonNull T resource,\n        @NonNull Object model,\n        Target<T> target,\n        @NonNull DataSource dataSource,\n        boolean isFirst) {\n      try {\n        completer.set(new TargetAndResult<>(target, resource));\n      } catch (Throwable t) {\n        completer.setException(t);\n      }\n      return true;\n    }\n  }\n\n  private static final class TargetAndResult<T> {\n    private final Target<T> target;\n    private final T result;\n\n    TargetAndResult(Target<T> target, T result) {\n      this.target = target;\n      this.result = result;\n    }\n  }\n\n  private GlideFutures() {}\n}\n"
  },
  {
    "path": "integration/concurrent/src/test/java/com/bumptech/glide/integration/concurrent/GlideFuturesTest.java",
    "content": "package com.bumptech.glide.integration.concurrent;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.MockGlideExecutor;\nimport com.bumptech.glide.testutil.MockModelLoader;\nimport com.google.common.util.concurrent.Futures;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.common.util.concurrent.SettableFuture;\nimport java.io.IOException;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic final class GlideFuturesTest {\n\n  private Context app;\n\n  @Before\n  public void setUp() {\n    app = ApplicationProvider.getApplicationContext();\n\n    GlideExecutor executor = MockGlideExecutor.newMainThreadExecutor();\n    Glide.init(\n        app,\n        new GlideBuilder()\n            .setAnimationExecutor(executor)\n            .setSourceExecutor(executor)\n            .setDiskCacheExecutor(executor));\n  }\n\n  @Test\n  public void testBaseLoad() throws Exception {\n    ColorDrawable expected = new ColorDrawable(Color.RED);\n    ListenableFuture<Drawable> future = GlideFutures.submit(Glide.with(app).load(expected));\n    assertThat(((ColorDrawable) Futures.getDone(future)).getColor()).isEqualTo(expected.getColor());\n  }\n\n  @Test\n  public void testErrorLoad() {\n    // Load some unsupported model.\n    final ListenableFuture<Bitmap> future =\n        GlideFutures.submit(Glide.with(app).asBitmap().load(app));\n    // Make sure that it throws.\n    assertThrows(\n        ExecutionException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            Futures.getDone(future);\n          }\n        });\n  }\n\n  @Test\n  public void testToString() throws Exception {\n    Foo model = new Foo();\n    SettableFuture<Bar> bar = SettableFuture.create();\n\n    Glide.get(app)\n        .getRegistry()\n        .prepend(\n            Bar.class,\n            Baz.class,\n            new ResourceDecoder<Bar, Baz>() {\n\n              @Override\n              public boolean handles(Bar source, Options options) throws IOException {\n                return true;\n              }\n\n              @Override\n              public Resource<Baz> decode(Bar source, int width, int height, Options options)\n                  throws IOException {\n                throw new IOException();\n              }\n            });\n    MockModelLoader.mockAsync(model, Bar.class, bar);\n    ListenableFuture<Baz> future =\n        GlideFutures.submit(\n            Glide.with(app)\n                .as(Baz.class)\n                .load(model)\n                .skipMemoryCache(true)\n                .diskCacheStrategy(DiskCacheStrategy.NONE));\n    assertThat(future.toString()).contains(\"Foo\");\n    future.cancel(true);\n    assertThat(bar.isCancelled()).isTrue();\n  }\n\n  private static final class Foo {}\n\n  private static final class Bar {}\n\n  private static final class Baz {}\n}\n"
  },
  {
    "path": "integration/cronet/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.cronet\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = 16\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    implementation(libs.cronet)\n    implementation(libs.guava)\n    implementation(project(\":annotation\"))\n    annotationProcessor(project(\":annotation:compiler\"))\n\n    api(libs.androidx.annotation)\n\n    testImplementation(libs.truth)\n    testImplementation(libs.junit)\n    testImplementation(libs.robolectric)\n    testImplementation(libs.mockito.core)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/cronet/gradle.properties",
    "content": "POM_NAME=Glide Cronet Integration\nPOM_ARTIFACT_ID=cronet-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to use Cronet to fetch data over http/https in Glide\n"
  },
  {
    "path": "integration/cronet/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n</lint>\n"
  },
  {
    "path": "integration/cronet/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application>\n        <meta-data\n            android:name=\"com.bumptech.glide.integration.cronet.CronetGlideModule\"\n            android:value=\"GlideModule\" />\n    </application>\n</manifest>\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/BufferQueue.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport java.nio.ByteBuffer;\nimport java.util.ArrayDeque;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.chromium.net.UrlResponseInfo;\n\n/**\n * A utility for processing response bodies, as one contiguous buffer rather than an asynchronous\n * stream.\n */\nfinal class BufferQueue {\n  public static final String CONTENT_LENGTH = \"content-length\";\n  public static final String CONTENT_ENCODING = \"content-encoding\";\n  private static final int DEFAULT_BUFFER_SIZE = 16384;\n  private final Queue<ByteBuffer> buffers;\n  private final AtomicBoolean isCoalesced = new AtomicBoolean(false);\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Use this class during a request, to combine streamed buffers of a response into a single final\n   * buffer.\n   *\n   * <p>For example: {@code @Override public void onResponseStarted(UrlRequest request,\n   * UrlResponseInfo info) { request.read(builder.getFirstBuffer(info)); } @Override public void\n   * onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer buffer) {\n   * request.read(builder.getNextBuffer(buffer)); } }\n   */\n  public static final class Builder {\n    private ArrayDeque<ByteBuffer> buffers = new ArrayDeque<>();\n    private RuntimeException whenClosed;\n\n    private Builder() {}\n\n    /** Returns the next buffer to write data into. */\n    public ByteBuffer getNextBuffer(ByteBuffer lastBuffer) {\n      if (buffers == null) {\n        throw new RuntimeException(whenClosed);\n      }\n      if (lastBuffer != buffers.peekLast()) {\n        buffers.addLast(lastBuffer);\n      }\n      if (lastBuffer.hasRemaining()) {\n        return lastBuffer;\n      } else {\n        return ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);\n      }\n    }\n\n    /** Returns a ByteBuffer heuristically sized to hold the whole response body. */\n    public ByteBuffer getFirstBuffer(UrlResponseInfo info) {\n      // Security note - a malicious server could attempt to exhaust client memory by sending\n      // down a Content-Length of a very large size, which we would eagerly allocate without\n      // the server having to actually send those bytes. This isn't considered to be an\n      // issue, because that same malicious server could use our transparent gzip to force us\n      // to allocate 1032 bytes per byte sent by the server.\n      return ByteBuffer.allocateDirect((int) Math.min(bufferSizeHeuristic(info), 524288));\n    }\n\n    @SuppressWarnings(\"checkstyle:UnnecessaryParentheses\") // Readability\n    private static long bufferSizeHeuristic(UrlResponseInfo info) {\n      final Map<String, List<String>> headers = info.getAllHeaders();\n      if (headers.containsKey(CONTENT_LENGTH)) {\n        long contentLength = Long.parseLong(headers.get(CONTENT_LENGTH).get(0));\n        boolean isCompressed =\n            !headers.containsKey(CONTENT_ENCODING)\n                || (headers.get(CONTENT_ENCODING).size() == 1\n                    && \"identity\".equals(headers.get(CONTENT_ENCODING).get(0)));\n        if (isCompressed) {\n          // We have to guess at the uncompressed size. In the future, consider guessing a\n          // compression ratio based on the content-type and content-encoding. For now,\n          // assume 2.\n          return 2 * contentLength;\n        } else {\n          // In this case, we know exactly how many bytes we're going to get, so we can\n          // size our buffer perfectly. However, we still have to call read() for the last time,\n          // even when we know there shouldn't be any more bytes coming. To avoid allocating another\n          // buffer for that case, add one more byte than we really need.\n          return contentLength + 1;\n        }\n      } else {\n        // No content-length. This means we're either being sent a chunked response, or the\n        // java stack stripped content length because of transparent gzip. In either case we really\n        // have no idea, and so we fall back to a reasonable guess.\n        return DEFAULT_BUFFER_SIZE;\n      }\n    }\n\n    public BufferQueue build() {\n      whenClosed = new RuntimeException();\n      final ArrayDeque<ByteBuffer> buffers = this.buffers;\n      this.buffers = null;\n      return new BufferQueue(buffers);\n    }\n  }\n\n  private BufferQueue(Queue<ByteBuffer> buffers) {\n    this.buffers = buffers;\n    for (ByteBuffer buffer : this.buffers) {\n      buffer.flip();\n    }\n  }\n\n  /** Returns the response body as a single contiguous buffer. */\n  public ByteBuffer coalesceToBuffer() {\n    markCoalesced();\n    if (buffers.size() == 0) {\n      return ByteBuffer.allocateDirect(0);\n    } else if (buffers.size() == 1) {\n      return buffers.remove();\n    } else {\n      int size = 0;\n      for (ByteBuffer buffer : buffers) {\n        size += buffer.remaining();\n      }\n      ByteBuffer result = ByteBuffer.allocateDirect(size);\n      while (!buffers.isEmpty()) {\n        result.put(buffers.remove());\n      }\n      result.flip();\n      return result;\n    }\n  }\n\n  private void markCoalesced() {\n    if (!isCoalesced.compareAndSet(false, true)) {\n      throw new IllegalStateException(\"This BufferQueue has already been consumed\");\n    }\n  }\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/ByteBufferParser.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport java.nio.ByteBuffer;\n\n/**\n * Parses a {@link java.nio.ByteBuffer} to a particular data type.\n *\n * @param <T> The type of data to parse the buffer to.\n */\ninterface ByteBufferParser<T> {\n  /** Returns the required type of data parsed from the given {@link ByteBuffer}. */\n  T parse(ByteBuffer byteBuffer);\n\n  /** Returns the {@link Class} of the data that will be parsed from {@link ByteBuffer}s. */\n  Class<T> getDataClass();\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/ChromiumRequestSerializer.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport android.util.Log;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.HttpException;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.google.common.base.Supplier;\nimport com.google.common.base.Suppliers;\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.EnumMap;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\nimport org.chromium.net.CronetException;\nimport org.chromium.net.UrlRequest;\nimport org.chromium.net.UrlRequest.Callback;\nimport org.chromium.net.UrlResponseInfo;\n\n/**\n * Ensures that two simultaneous requests for exactly the same url make only a single http request.\n *\n * <p>Requests are started by Glide on multiple threads in a thread pool. An arbitrary number of\n * threads may attempt to start or cancel requests for one or more urls at once. Our goal is to\n * ensure:\n * <li>\n *\n *     <ul>\n *       A new request is made to cronet if a url is requested and no cronet request for that url is\n *       in progress\n * </ul>\n *\n * <ul>\n *   Subsequent requests for in progress urls do not make new requests to cronet, but are notified\n *   when the existing cronet request completes.\n * </ul>\n *\n * <ul>\n *   Cancelling a single request does not cancel the cronet request if multiple requests for the url\n *   have been made, but cancelling all requests for a url does cancel the cronet request.\n * </ul>\n */\nfinal class ChromiumRequestSerializer {\n  private static final String TAG = \"ChromiumSerializer\";\n\n  private static final Map<Priority, Integer> GLIDE_TO_CHROMIUM_PRIORITY =\n      new EnumMap<>(Priority.class);\n  // Memoized so that all callers can share an instance.\n  // Suppliers.memoize() is thread safe. See google3/java/com/google/common/base/Suppliers.java\n  private static final Supplier<Executor> GLIDE_EXECUTOR_SUPPLIER =\n      Suppliers.memoize(\n          new Supplier<Executor>() {\n            @Override\n            public GlideExecutor get() {\n              // Allow network operations, but use a single thread. See b/37684357.\n              return GlideExecutor.newSourceExecutor(\n                  1 /*threadCount*/, \"chromium-serializer\", UncaughtThrowableStrategy.DEFAULT);\n            }\n          });\n\n  private abstract static class PriorityRunnable implements Runnable, Comparable<PriorityRunnable> {\n\n    private final int priority;\n\n    private PriorityRunnable(Priority priority) {\n      this.priority = priority.ordinal();\n    }\n\n    @Override\n    public final int compareTo(PriorityRunnable another) {\n      if (another.priority > this.priority) {\n        return -1;\n      } else if (another.priority < this.priority) {\n        return 1;\n      }\n      return 0;\n    }\n  }\n\n  static {\n    GLIDE_TO_CHROMIUM_PRIORITY.put(Priority.IMMEDIATE, UrlRequest.Builder.REQUEST_PRIORITY_HIGHEST);\n    GLIDE_TO_CHROMIUM_PRIORITY.put(Priority.HIGH, UrlRequest.Builder.REQUEST_PRIORITY_MEDIUM);\n    GLIDE_TO_CHROMIUM_PRIORITY.put(Priority.NORMAL, UrlRequest.Builder.REQUEST_PRIORITY_LOW);\n    GLIDE_TO_CHROMIUM_PRIORITY.put(Priority.LOW, UrlRequest.Builder.REQUEST_PRIORITY_LOWEST);\n  }\n\n  private final JobPool jobPool;\n  private final Map<GlideUrl, Job> jobs = new HashMap<>();\n  private final CronetRequestFactory requestFactory;\n  @Nullable private final DataLogger dataLogger;\n\n  ChromiumRequestSerializer(\n      CronetRequestFactory requestFactory,\n      @Nullable DataLogger dataLogger,\n      @Nullable final GlideExecutor executor) {\n    this.requestFactory = requestFactory;\n    this.dataLogger = dataLogger;\n    if (executor == null) {\n      this.jobPool = new JobPool(GLIDE_EXECUTOR_SUPPLIER);\n    } else {\n      this.jobPool =\n          new JobPool(\n              new Supplier<Executor>() {\n                @Override\n                public Executor get() {\n                  return executor;\n                }\n              });\n    }\n  }\n\n  void startRequest(Priority priority, GlideUrl glideUrl, Listener listener) {\n    boolean startNewRequest = false;\n    Job job;\n    synchronized (this) {\n      job = jobs.get(glideUrl);\n      if (job == null) {\n        startNewRequest = true;\n        job = jobPool.get(glideUrl);\n        jobs.put(glideUrl, job);\n      }\n      job.addListener(listener);\n    }\n\n    if (startNewRequest) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Fetching image url using cronet\" + \" url: \" + glideUrl);\n      }\n      job.priority = priority;\n      job.request =\n          requestFactory\n              .newRequest(\n                  glideUrl.toStringUrl(),\n                  GLIDE_TO_CHROMIUM_PRIORITY.get(priority),\n                  glideUrl.getHeaders(),\n                  job)\n              .build();\n      job.request.start();\n\n      // It's possible we will be cancelled between adding the job to the job list and starting the\n      // corresponding request. We don't want to hold a lock while starting the request, because\n      // starting the request may block for a while and we need cancellation to happen quickly (it\n      // happens on the main thread).\n      if (job.isCancelled) {\n        job.request.cancel();\n      }\n    }\n  }\n\n  void cancelRequest(GlideUrl glideUrl, Listener listener) {\n    final Job job;\n    synchronized (this) {\n      job = jobs.get(glideUrl);\n    }\n    // Jobs may be cancelled before they are started.\n    if (job != null) {\n      job.removeListener(listener);\n    }\n  }\n\n  private static IOException getExceptionIfFailed(\n      UrlResponseInfo info, IOException e, boolean wasCancelled) {\n    if (wasCancelled) {\n      return null;\n    } else if (e != null) {\n      return e;\n    } else if (info.getHttpStatusCode() != HttpURLConnection.HTTP_OK) {\n      return new HttpException(info.getHttpStatusCode());\n    }\n    return null;\n  }\n\n  /**\n   * Manages a single cronet request for a single url with one or more active listeners.\n   *\n   * <p>Cronet requests are cancelled when all listeners are removed.\n   */\n  private class Job extends Callback {\n    private final List<Listener> listeners = new ArrayList<>(2);\n\n    private GlideUrl glideUrl;\n    private Priority priority;\n    private long startTime;\n    private UrlRequest request;\n    private long endTimeMs;\n    private long responseStartTimeMs;\n    private volatile boolean isCancelled;\n    private BufferQueue.Builder builder;\n    private final Supplier<Executor> executorSupplier;\n\n    Job(Supplier<Executor> executorSupplier) {\n      this.executorSupplier = executorSupplier;\n    }\n\n    void init(GlideUrl glideUrl) {\n      startTime = System.currentTimeMillis();\n      this.glideUrl = glideUrl;\n    }\n\n    void addListener(Listener listener) {\n      synchronized (ChromiumRequestSerializer.this) {\n        listeners.add(listener);\n      }\n    }\n\n    void removeListener(Listener listener) {\n      synchronized (ChromiumRequestSerializer.this) {\n        // Note: multiple cancellation calls + a subsequent request for a url may mean we fail to\n        // remove the listener here because that listener is actually for a previous request. Since\n        // that race is harmless, we simply ignore it.\n        listeners.remove(listener);\n        if (listeners.isEmpty()) {\n          isCancelled = true;\n          jobs.remove(glideUrl);\n        }\n      }\n\n      // The request may not have started yet, so request may be null.\n      if (isCancelled) {\n        UrlRequest localRequest = request;\n        if (localRequest != null) {\n          localRequest.cancel();\n        }\n      }\n    }\n\n    @Override\n    public void onRedirectReceived(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, String s)\n        throws Exception {\n      urlRequest.followRedirect();\n    }\n\n    @Override\n    public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {\n      responseStartTimeMs = System.currentTimeMillis();\n      builder = BufferQueue.builder();\n      request.read(builder.getFirstBuffer(info));\n    }\n\n    @Override\n    public void onReadCompleted(\n        UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, ByteBuffer byteBuffer)\n        throws Exception {\n      request.read(builder.getNextBuffer(byteBuffer));\n    }\n\n    @Override\n    public void onSucceeded(UrlRequest request, final UrlResponseInfo info) {\n      executorSupplier\n          .get()\n          .execute(\n              new PriorityRunnable(priority) {\n                @Override\n                public void run() {\n                  onRequestFinished(\n                      info,\n                      null /*exception*/,\n                      false /*wasCancelled*/,\n                      builder.build().coalesceToBuffer());\n                }\n              });\n    }\n\n    @Override\n    public void onFailed(\n        UrlRequest urlRequest, final UrlResponseInfo urlResponseInfo, final CronetException e) {\n      executorSupplier\n          .get()\n          .execute(\n              new PriorityRunnable(priority) {\n                @Override\n                public void run() {\n                  onRequestFinished(urlResponseInfo, e, false /*wasCancelled*/, null /*buffer*/);\n                }\n              });\n    }\n\n    @Override\n    public void onCanceled(UrlRequest urlRequest, @Nullable final UrlResponseInfo urlResponseInfo) {\n      executorSupplier\n          .get()\n          .execute(\n              new PriorityRunnable(priority) {\n                @Override\n                public void run() {\n                  onRequestFinished(\n                      urlResponseInfo, null /*exception*/, true /*wasCancelled*/, null /*buffer*/);\n                }\n              });\n    }\n\n    private void onRequestFinished(\n        UrlResponseInfo info,\n        @Nullable CronetException e,\n        boolean wasCancelled,\n        ByteBuffer buffer) {\n      synchronized (ChromiumRequestSerializer.this) {\n        jobs.remove(glideUrl);\n      }\n\n      Exception exception = getExceptionIfFailed(info, e, wasCancelled);\n      boolean isSuccess = exception == null && !wasCancelled;\n\n      endTimeMs = System.currentTimeMillis();\n\n      maybeLogResult(isSuccess, exception, wasCancelled, buffer);\n      if (isSuccess) {\n        notifySuccess(buffer);\n      } else {\n        notifyFailure(exception);\n      }\n\n      if (dataLogger != null) {\n        dataLogger.logNetworkData(info, startTime, responseStartTimeMs, endTimeMs);\n      }\n      builder = null;\n\n      jobPool.put(this);\n    }\n\n    private void notifySuccess(ByteBuffer buffer) {\n      ByteBuffer toNotify = buffer;\n      /* Locking here isn't necessary and is potentially dangerous. There's an optimization in\n       * Glide that avoids re-posting results if the callback onRequestComplete triggers is called\n       * on the calling thread. If that were ever to happen here (the request is cached in memory?),\n       * this might block all requests for a while. Locking isn't necessary because the Job is\n       * removed from the serializer's job set at the beginning of onRequestFinished. After that\n       * point, whatever thread we're on is the only one that has access to the Job. Subsequent\n       * requests for the same image would trigger an additional RPC/Job. */\n      for (int i = 0, size = listeners.size(); i < size; i++) {\n        Listener listener = listeners.get(i);\n        listener.onRequestComplete(toNotify);\n        toNotify = (ByteBuffer) toNotify.asReadOnlyBuffer().position(0);\n      }\n    }\n\n    private void notifyFailure(Exception exception) {\n      /* Locking here isn't necessary and is potentially dangerous. There's an optimization in\n       * Glide that avoids re-posting results if the callback onRequestComplete triggers is called\n       * on the calling thread. If that were ever to happen here (the request is cached in memory?),\n       * this might block all requests for a while. Locking isn't necessary because the Job is\n       * removed from the serializer's job set at the beginning of onRequestFinished. After that\n       * point, whatever thread we're on is the only one that has access to the Job. Subsequent\n       * requests for the same image would trigger an additional RPC/Job. */\n      for (int i = 0, size = listeners.size(); i < size; i++) {\n        Listener listener = listeners.get(i);\n        listener.onRequestFailed(exception);\n      }\n    }\n\n    private void maybeLogResult(\n        boolean isSuccess, Exception exception, boolean wasCancelled, ByteBuffer buffer) {\n      if (isSuccess && Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(\n            TAG,\n            \"Successfully completed request\"\n                + \", url: \"\n                + glideUrl\n                + \", duration: \"\n                + (System.currentTimeMillis() - startTime)\n                + \", file size: \"\n                + (buffer.limit() / 1024)\n                + \"kb\");\n      } else if (!isSuccess && Log.isLoggable(TAG, Log.ERROR) && !wasCancelled) {\n        Log.e(TAG, \"Request failed, url: \" + glideUrl, exception);\n      }\n    }\n\n    private void clearListeners() {\n      synchronized (ChromiumRequestSerializer.this) {\n        listeners.clear();\n        request = null;\n        isCancelled = false;\n      }\n    }\n  }\n\n  private class JobPool {\n    private static final int MAX_POOL_SIZE = 50;\n    private final ArrayDeque<Job> pool = new ArrayDeque<>();\n    private final Supplier<Executor> executorSupplier;\n\n    public JobPool(Supplier<Executor> executorSupplier) {\n      this.executorSupplier = executorSupplier;\n    }\n\n    public synchronized Job get(GlideUrl glideUrl) {\n      Job job = pool.poll();\n      if (job == null) {\n        job = new Job(executorSupplier);\n      }\n      job.init(glideUrl);\n      return job;\n    }\n\n    public void put(Job job) {\n      job.clearListeners();\n      synchronized (this) {\n        if (pool.size() < MAX_POOL_SIZE) {\n          pool.offer(job);\n        }\n      }\n    }\n  }\n\n  interface Listener {\n    void onRequestComplete(ByteBuffer byteBuffer);\n\n    void onRequestFailed(@Nullable Exception e);\n  }\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/ChromiumUrlFetcher.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport java.nio.ByteBuffer;\n\n/** An {@link DataFetcher} for fetching {@link GlideUrl} using cronet. */\nfinal class ChromiumUrlFetcher<T> implements DataFetcher<T>, ChromiumRequestSerializer.Listener {\n\n  private final ChromiumRequestSerializer serializer;\n  private final ByteBufferParser<T> parser;\n  private final GlideUrl url;\n\n  private DataCallback<? super T> callback;\n\n  public ChromiumUrlFetcher(\n      ChromiumRequestSerializer serializer, ByteBufferParser<T> parser, GlideUrl url) {\n    this.serializer = serializer;\n    this.parser = parser;\n    this.url = url;\n  }\n\n  @Override\n  public void loadData(Priority priority, DataCallback<? super T> callback) {\n    this.callback = callback;\n    serializer.startRequest(priority, url, this);\n  }\n\n  @Override\n  public void cleanup() {\n    // Nothing to cleanup.\n  }\n\n  @Override\n  public void cancel() {\n    serializer.cancelRequest(url, this);\n  }\n\n  @Override\n  public Class<T> getDataClass() {\n    return parser.getDataClass();\n  }\n\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.REMOTE;\n  }\n\n  @Override\n  public void onRequestComplete(ByteBuffer byteBuffer) {\n    callback.onDataReady(parser.parse(byteBuffer));\n  }\n\n  @Override\n  public void onRequestFailed(@Nullable Exception e) {\n    callback.onLoadFailed(e);\n  }\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/ChromiumUrlLoader.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\n\n/**\n * A {@link com.bumptech.glide.load.model.ModelLoader} for loading urls using cronet.\n *\n * <p>You can optionally pass an executor to the constructor for handling cronet callbacks in {@link\n * ChromiumRequestSerializer}. If the executor is not provided, it will be created for you.\n *\n * @param <T> The type of data this loader will load.\n */\npublic final class ChromiumUrlLoader<T> implements ModelLoader<GlideUrl, T> {\n  private final ChromiumRequestSerializer requestSerializer;\n  private final ByteBufferParser<T> parser;\n\n  ChromiumUrlLoader(CronetRequestFactory requestFactory, ByteBufferParser<T> parser) {\n    this(parser, requestFactory, null /*dataLogger*/);\n  }\n\n  ChromiumUrlLoader(\n      ByteBufferParser<T> parser,\n      CronetRequestFactory requestFactory,\n      @Nullable DataLogger dataLogger) {\n    this.parser = parser;\n    requestSerializer =\n        new ChromiumRequestSerializer(requestFactory, dataLogger, /* executor= */ null);\n  }\n\n  ChromiumUrlLoader(\n      ByteBufferParser<T> parser,\n      CronetRequestFactory requestFactory,\n      @Nullable DataLogger dataLogger,\n      @Nullable GlideExecutor executor) {\n    this.parser = parser;\n    requestSerializer = new ChromiumRequestSerializer(requestFactory, dataLogger, executor);\n  }\n\n  @Override\n  public LoadData<T> buildLoadData(GlideUrl glideUrl, int width, int height, Options options) {\n    DataFetcher<T> fetcher = new ChromiumUrlFetcher<>(requestSerializer, parser, glideUrl);\n    return new LoadData<>(glideUrl, fetcher);\n  }\n\n  @Override\n  public boolean handles(GlideUrl glideUrl) {\n    return true;\n  }\n\n  /** Loads {@link InputStream}s for {@link GlideUrl}s using cronet. */\n  public static final class StreamFactory\n      implements ModelLoaderFactory<GlideUrl, InputStream>, ByteBufferParser<InputStream> {\n\n    private CronetRequestFactory requestFactory;\n    @Nullable private final DataLogger dataLogger;\n    @Nullable private final GlideExecutor executor;\n\n    public StreamFactory(CronetRequestFactory requestFactory, @Nullable DataLogger dataLogger) {\n      this.requestFactory = requestFactory;\n      this.dataLogger = dataLogger;\n      this.executor = null;\n    }\n\n    /**\n     * @param executor See {@link ChromiumUrlLoader} for details.\n     */\n    public StreamFactory(\n        CronetRequestFactory requestFactory,\n        @Nullable DataLogger dataLogger,\n        @Nullable GlideExecutor executor) {\n      this.requestFactory = requestFactory;\n      this.dataLogger = dataLogger;\n      this.executor = executor;\n    }\n\n    @Override\n    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new ChromiumUrlLoader<>(/* parser= */ this, requestFactory, dataLogger, executor);\n    }\n\n    @Override\n    public void teardown() {}\n\n    @Override\n    public InputStream parse(ByteBuffer byteBuffer) {\n      return ByteBufferUtil.toStream(byteBuffer);\n    }\n\n    @Override\n    public Class<InputStream> getDataClass() {\n      return InputStream.class;\n    }\n  }\n\n  /** Loads {@link ByteBuffer}s for {@link GlideUrl}s using cronet. */\n  public static final class ByteBufferFactory\n      implements ModelLoaderFactory<GlideUrl, ByteBuffer>, ByteBufferParser<ByteBuffer> {\n\n    private CronetRequestFactory requestFactory;\n    @Nullable private final DataLogger dataLogger;\n    @Nullable private final GlideExecutor executor;\n\n    public ByteBufferFactory(CronetRequestFactory requestFactory, @Nullable DataLogger dataLogger) {\n      this.requestFactory = requestFactory;\n      this.dataLogger = dataLogger;\n      this.executor = null;\n    }\n\n    /**\n     * @param executor See {@link ChromiumUrlLoader} for details.\n     */\n    public ByteBufferFactory(\n        CronetRequestFactory requestFactory,\n        @Nullable DataLogger dataLogger,\n        @Nullable GlideExecutor executor) {\n      this.requestFactory = requestFactory;\n      this.dataLogger = dataLogger;\n      this.executor = executor;\n    }\n\n    @Override\n    public ModelLoader<GlideUrl, ByteBuffer> build(MultiModelLoaderFactory multiFactory) {\n      return new ChromiumUrlLoader<>(/* parser= */ this, requestFactory, dataLogger, executor);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n\n    @Override\n    public ByteBuffer parse(ByteBuffer byteBuffer) {\n      return byteBuffer;\n    }\n\n    @Override\n    public Class<ByteBuffer> getDataClass() {\n      return ByteBuffer.class;\n    }\n  }\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/CronetEngineSingleton.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport android.content.Context;\nimport org.chromium.net.CronetEngine;\n\n/**\n * Class controlling singleton instance of the CronetEngine. Ensures at most one instance of the\n * CronetEngine is created.\n */\n// NOTE: This is a standalone class and not a memoized supplier as the CronetEngine creations\n// requires a parameter, namedly a Context reference.\npublic final class CronetEngineSingleton {\n\n  // non instantiable\n  private CronetEngineSingleton() {}\n\n  private static volatile CronetEngine cronetEngineSingleton;\n\n  public static CronetEngine getSingleton(Context context) {\n\n    // Lazily create the engine.\n    if (cronetEngineSingleton == null) {\n      synchronized (CronetEngineSingleton.class) {\n        // have to re-check since this might have changed before synchronization, but we don't\n        // want to synchronize just to check for null.\n        if (cronetEngineSingleton == null) {\n          cronetEngineSingleton = createEngine(context);\n        }\n      }\n    }\n\n    return cronetEngineSingleton;\n  }\n\n  private static CronetEngine createEngine(Context context) {\n    return new CronetEngine.Builder(context)\n        .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISABLED, 0)\n        .enableHttp2(true)\n        .enableQuic(false)\n        .build();\n  }\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/CronetGlideModule.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport android.content.Context;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.module.GlideModule;\nimport com.google.common.base.Supplier;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport org.chromium.net.CronetEngine;\n\n/**\n * A {@link GlideModule} that registers components allowing remote image fetching to be done using\n * Cronet.\n */\npublic final class CronetGlideModule implements GlideModule {\n\n  @Override\n  public void applyOptions(Context context, GlideBuilder builder) {}\n\n  @Override\n  public void registerComponents(final Context context, Glide glide, Registry registry) {\n    CronetRequestFactory factory =\n        new CronetRequestFactoryImpl(\n            new Supplier<CronetEngine>() {\n              @Override\n              public CronetEngine get() {\n                return CronetEngineSingleton.getSingleton(context);\n              }\n            });\n    registry.replace(\n        GlideUrl.class, InputStream.class, new ChromiumUrlLoader.StreamFactory(factory, null));\n    registry.prepend(\n        GlideUrl.class, ByteBuffer.class, new ChromiumUrlLoader.ByteBufferFactory(factory, null));\n  }\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/CronetLibraryGlideModule.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.integration.cronet.ChromiumUrlLoader.ByteBufferFactory;\nimport com.bumptech.glide.integration.cronet.ChromiumUrlLoader.StreamFactory;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.module.LibraryGlideModule;\nimport com.google.common.base.Supplier;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport org.chromium.net.CronetEngine;\n\n/**\n * A {@link LibraryGlideModule} that registers components allowing remote image fetching to be done\n * using Cronet.\n */\n@GlideModule\npublic final class CronetLibraryGlideModule extends LibraryGlideModule {\n\n  @Override\n  public void registerComponents(\n      final @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    CronetRequestFactory factory =\n        new CronetRequestFactoryImpl(\n            new Supplier<CronetEngine>() {\n              @Override\n              public CronetEngine get() {\n                return CronetEngineSingleton.getSingleton(context);\n              }\n            });\n    registry.replace(\n        GlideUrl.class, InputStream.class, new StreamFactory(factory, null /* dataLogger */));\n    registry.prepend(\n        GlideUrl.class, ByteBuffer.class, new ByteBufferFactory(factory, null /* dataLogger */));\n  }\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/CronetRequestFactory.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport java.util.Map;\nimport org.chromium.net.UrlRequest;\n\n/** Factory to build custom cronet requests. */\npublic interface CronetRequestFactory {\n\n  UrlRequest.Builder newRequest(\n      String url, int requestPriority, Map<String, String> headers, UrlRequest.Callback listener);\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/CronetRequestFactoryImpl.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport com.google.common.base.Supplier;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\nimport org.chromium.net.CronetEngine;\nimport org.chromium.net.UrlRequest;\n\n/** Default implementation for building cronet requests. */\npublic final class CronetRequestFactoryImpl implements CronetRequestFactory {\n\n  private final Supplier<CronetEngine> cronetEngineGetter;\n\n  public CronetRequestFactoryImpl(Supplier<CronetEngine> cronetEngineGetter) {\n    this.cronetEngineGetter = cronetEngineGetter;\n  }\n\n  @Override\n  public UrlRequest.Builder newRequest(\n      String url, int requestPriority, Map<String, String> headers, UrlRequest.Callback listener) {\n    CronetEngine engine = cronetEngineGetter.get();\n    UrlRequest.Builder builder =\n        engine.newUrlRequestBuilder(\n            url,\n            listener,\n            new Executor() {\n              @Override\n              public void execute(Runnable runnable) {\n                runnable.run();\n              }\n            });\n    builder.allowDirectExecutor();\n    builder.setPriority(requestPriority);\n    for (Map.Entry<String, String> header : headers.entrySet()) {\n      // Cronet owns the Accept-Encoding header and user agent\n      String key = header.getKey();\n      if (\"Accept-Encoding\".equalsIgnoreCase(key) || \"User-Agent\".equalsIgnoreCase(key)) {\n        continue;\n      }\n      builder.addHeader(key, header.getValue());\n    }\n    return builder;\n  }\n}\n"
  },
  {
    "path": "integration/cronet/src/main/java/com/bumptech/glide/integration/cronet/DataLogger.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport androidx.annotation.Nullable;\nimport org.chromium.net.UrlResponseInfo;\n\n/** A interface for logging data information related to loading the data. */\npublic interface DataLogger {\n\n  /**\n   * Logs the related network information.\n   *\n   * @param httpUrlRequest HttpUrlRequest that contains information on the request. May be {@code\n   *     null} if the request was cancelled.\n   * @param startTimeMs Timestamp (ms) that the request started.\n   * @param responseStartTimeMs Timestamp (ms) when the first header byte was received.\n   * @param endTimeMs Timestamp (ms) that the request ended.\n   */\n  void logNetworkData(\n      @Nullable UrlResponseInfo httpUrlRequest,\n      long startTimeMs,\n      long responseStartTimeMs,\n      long endTimeMs);\n}\n"
  },
  {
    "path": "integration/cronet/src/test/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"26\"/>\n</manifest>\n"
  },
  {
    "path": "integration/cronet/src/test/java/com/bumptech/glide/integration/cronet/ChromiumUrlFetcherTest.java",
    "content": "package com.bumptech.glide.integration.cronet;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyMap;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.HttpException;\nimport com.bumptech.glide.load.data.DataFetcher.DataCallback;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.LazyHeaders;\nimport com.bumptech.glide.load.model.LazyHeaders.Builder;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.net.HttpURLConnection;\nimport java.nio.ByteBuffer;\nimport java.util.AbstractMap.SimpleImmutableEntry;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\nimport org.chromium.net.CronetEngine;\nimport org.chromium.net.CronetException;\nimport org.chromium.net.UrlRequest;\nimport org.chromium.net.UrlRequest.Callback;\nimport org.chromium.net.UrlResponseInfo;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\n\n/** Tests for {@link ChromiumUrlFetcher}. */\n@RunWith(RobolectricTestRunner.class)\npublic class ChromiumUrlFetcherTest {\n\n  @Rule public final MockitoRule mocks = MockitoJUnit.rule();\n  @Mock private DataCallback<ByteBuffer> callback;\n  @Mock private CronetEngine cronetEngine;\n  @Mock private UrlRequest request;\n  @Mock private UrlRequest.Builder mockUrlRequestBuilder;\n  @Mock private ByteBufferParser<ByteBuffer> parser;\n  @Mock private CronetRequestFactory cronetRequestFactory;\n  @Mock private DataCallback<ByteBuffer> firstCallback;\n  @Mock private DataCallback<ByteBuffer> secondCallback;\n\n  private UrlRequest.Builder builder;\n  private GlideUrl glideUrl;\n  private ChromiumUrlFetcher<ByteBuffer> fetcher;\n  private ChromiumRequestSerializer serializer;\n  private ArgumentCaptor<UrlRequest.Callback> urlRequestListenerCaptor;\n\n  @Before\n  public void setUp() {\n    when(parser.getDataClass()).thenReturn(ByteBuffer.class);\n    when(parser.parse(any(ByteBuffer.class)))\n        .thenAnswer(\n            new Answer<ByteBuffer>() {\n              @Override\n              public ByteBuffer answer(InvocationOnMock invocation) throws Throwable {\n                return (ByteBuffer) invocation.getArguments()[0];\n              }\n            });\n    when(cronetEngine.newUrlRequestBuilder(\n            anyString(), any(UrlRequest.Callback.class), any(Executor.class)))\n        .thenReturn(mockUrlRequestBuilder);\n    when(mockUrlRequestBuilder.build()).thenReturn(request);\n\n    glideUrl = new GlideUrl(\"http://www.google.com\");\n\n    urlRequestListenerCaptor = ArgumentCaptor.forClass(UrlRequest.Callback.class);\n    serializer =\n        new ChromiumRequestSerializer(\n            cronetRequestFactory, /* dataLogger= */ null, /* executor= */ null);\n    fetcher = new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n    builder =\n        cronetEngine.newUrlRequestBuilder(\n            glideUrl.toStringUrl(),\n            mock(UrlRequest.Callback.class),\n            MoreExecutors.directExecutor());\n    when(cronetRequestFactory.newRequest(\n            anyString(), anyInt(), anyHeaders(), urlRequestListenerCaptor.capture()))\n        .thenReturn(builder);\n    when(builder.build()).thenReturn(request);\n  }\n\n  @Test\n  public void testLoadData_createsAndStartsRequest() {\n    when(cronetRequestFactory.newRequest(\n            eq(glideUrl.toStringUrl()),\n            eq(UrlRequest.Builder.REQUEST_PRIORITY_LOWEST),\n            anyHeaders(),\n            any(UrlRequest.Callback.class)))\n        .thenReturn(builder);\n\n    fetcher.loadData(Priority.LOW, callback);\n\n    verify(request).start();\n  }\n\n  @Test\n  public void testLoadData_providesHeadersFromGlideUrl() {\n    LazyHeaders.Builder headersBuilder = new Builder();\n    headersBuilder.addHeader(\"key\", \"value\");\n    LazyHeaders headers = headersBuilder.build();\n\n    glideUrl = new GlideUrl(\"http://www.google.com\", headers);\n    fetcher = new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n    fetcher.loadData(Priority.LOW, callback);\n\n    verify(cronetRequestFactory)\n        .newRequest(\n            ArgumentMatchers.eq(glideUrl.toStringUrl()),\n            anyInt(),\n            ArgumentMatchers.eq(headers.getHeaders()),\n            any(UrlRequest.Callback.class));\n\n    verify(request).start();\n  }\n\n  @Test\n  public void testLoadData_withInProgressRequest_doesNotStartNewRequest() {\n    ChromiumUrlFetcher<ByteBuffer> firstFetcher =\n        new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n    ChromiumUrlFetcher<ByteBuffer> secondFetcher =\n        new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n\n    firstFetcher.loadData(Priority.LOW, callback);\n    secondFetcher.loadData(Priority.HIGH, callback);\n\n    verify(cronetRequestFactory, times(1))\n        .newRequest(\n            ArgumentMatchers.eq(glideUrl.toStringUrl()),\n            anyInt(),\n            ArgumentMatchers.<String, String>anyMap(),\n            any(UrlRequest.Callback.class));\n  }\n\n  @Test\n  public void testLoadData_withInProgressRequest_isNotifiedWhenRequestCompletes() throws Exception {\n    ChromiumUrlFetcher<ByteBuffer> firstFetcher =\n        new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n    ChromiumUrlFetcher<ByteBuffer> secondFetcher =\n        new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n\n    firstFetcher.loadData(Priority.LOW, firstCallback);\n    secondFetcher.loadData(Priority.HIGH, secondCallback);\n\n    succeed(getInfo(10, 200), urlRequestListenerCaptor.getValue(), ByteBuffer.allocateDirect(10));\n\n    verify(firstCallback, timeout(1000)).onDataReady(isA(ByteBuffer.class));\n    verify(secondCallback, timeout(1000)).onDataReady(isA(ByteBuffer.class));\n  }\n\n  @NonNull\n  private UrlResponseInfo getInfo(final int contentLength, final int statusCode) {\n    return new UrlResponseInfo() {\n\n      @Override\n      public String getUrl() {\n        return glideUrl.toStringUrl();\n      }\n\n      @Override\n      public List<String> getUrlChain() {\n        return ImmutableList.of(getUrl());\n      }\n\n      @Override\n      public int getHttpStatusCode() {\n        return statusCode;\n      }\n\n      @Override\n      public String getHttpStatusText() {\n        return \"OK\";\n      }\n\n      @Override\n      public List<Map.Entry<String, String>> getAllHeadersAsList() {\n        return ImmutableList.<Map.Entry<String, String>>of(\n            new SimpleImmutableEntry<>(\"Content-Length\", Integer.toString(contentLength)));\n      }\n\n      @Override\n      public Map<String, List<String>> getAllHeaders() {\n        ImmutableMap.Builder<String, List<String>> builder = ImmutableMap.builder();\n        for (Map.Entry<String, String> entry : getAllHeadersAsList()) {\n          builder.put(entry.getKey(), ImmutableList.copyOf(entry.getValue().split(\",\")));\n        }\n        return builder.build();\n      }\n\n      @Override\n      public boolean wasCached() {\n        return false;\n      }\n\n      @Override\n      public String getNegotiatedProtocol() {\n        return \"\";\n      }\n\n      @Override\n      public String getProxyServer() {\n        return \"\";\n      }\n\n      @Override\n      public long getReceivedByteCount() {\n        return 0;\n      }\n    };\n  }\n\n  @Test\n  public void testCancel_withMultipleInProgressRequests_doesNotCancelChromiumRequest() {\n    ChromiumUrlFetcher<ByteBuffer> firstFetcher =\n        new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n    ChromiumUrlFetcher<ByteBuffer> secondFetcher =\n        new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n\n    firstFetcher.loadData(Priority.LOW, callback);\n    secondFetcher.loadData(Priority.HIGH, callback);\n\n    firstFetcher.cancel();\n\n    verify(request, never()).cancel();\n  }\n\n  @Test\n  public void testCancel_afterCancellingAllInProgressRequests_cancelsChromiumRequest() {\n    ChromiumUrlFetcher<ByteBuffer> firstFetcher =\n        new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n    ChromiumUrlFetcher<ByteBuffer> secondFetcher =\n        new ChromiumUrlFetcher<>(serializer, parser, glideUrl);\n\n    firstFetcher.loadData(Priority.LOW, callback);\n    secondFetcher.loadData(Priority.HIGH, callback);\n\n    firstFetcher.cancel();\n    secondFetcher.cancel();\n\n    verify(request).cancel();\n  }\n\n  @Test\n  public void testCancel_withNoStartedRequest_doesNothing() {\n    fetcher.cancel();\n  }\n\n  @Test\n  public void testCancel_withStartedRequest_cancelsRequest() {\n    fetcher.loadData(Priority.LOW, callback);\n\n    fetcher.cancel();\n\n    verify(request).cancel();\n  }\n\n  @Test\n  public void testRequestComplete_withNonNullException_callsCallbackWithException() {\n    CronetException expected =\n        new CronetException(\"test\", /* cause= */ null) {\n          static final long serialVersionUID = 1;\n        };\n    fetcher.loadData(Priority.LOW, callback);\n    urlRequestListenerCaptor.getValue().onFailed(request, null, expected);\n\n    verify(callback, timeout(1000)).onLoadFailed(eq(expected));\n  }\n\n  @Test\n  public void testRequestComplete_withNon200StatusCode_callsCallbackWithException()\n      throws Exception {\n    UrlResponseInfo info = getInfo(0, HttpURLConnection.HTTP_INTERNAL_ERROR);\n    fetcher.loadData(Priority.LOW, callback);\n    UrlRequest.Callback urlCallback = urlRequestListenerCaptor.getValue();\n    succeed(info, urlCallback, ByteBuffer.allocateDirect(0));\n    ArgumentCaptor<HttpException> captor = ArgumentCaptor.forClass(HttpException.class);\n    verify(callback, timeout(1000)).onLoadFailed(captor.capture());\n    assertThat(captor.getValue())\n        .hasMessageThat()\n        .isEqualTo(\"Http request failed, status code: 500\");\n  }\n\n  private void succeed(UrlResponseInfo info, Callback urlCallback, ByteBuffer byteBuffer)\n      throws Exception {\n    byteBuffer.position(byteBuffer.limit());\n    urlCallback.onResponseStarted(request, info);\n    urlCallback.onReadCompleted(request, info, byteBuffer);\n    urlCallback.onSucceeded(request, info);\n  }\n\n  @Test\n  public void testRequestComplete_withUnauthorizedStatusCode_callsCallbackWithAuthError()\n      throws Exception {\n    UrlResponseInfo info = getInfo(0, HttpURLConnection.HTTP_FORBIDDEN);\n    fetcher.loadData(Priority.LOW, callback);\n    UrlRequest.Callback urlCallback = urlRequestListenerCaptor.getValue();\n    succeed(info, urlCallback, ByteBuffer.allocateDirect(0));\n\n    verifyAuthError();\n  }\n\n  @Test\n  public void testRequestComplete_whenCancelledAndUnauthorized_callsCallbackWithNullError()\n      throws Exception {\n    UrlResponseInfo info = getInfo(0, HttpURLConnection.HTTP_FORBIDDEN);\n    fetcher.loadData(Priority.HIGH, callback);\n    Callback urlCallback = urlRequestListenerCaptor.getValue();\n    urlCallback.onResponseStarted(request, info);\n    urlCallback.onCanceled(request, info);\n\n    verify(callback, timeout(1000)).onLoadFailed(ArgumentMatchers.<Exception>isNull());\n  }\n\n  private void verifyAuthError() {\n    ArgumentCaptor<Exception> exceptionArgumentCaptor = ArgumentCaptor.forClass(Exception.class);\n    verify(callback, timeout(1000)).onLoadFailed(exceptionArgumentCaptor.capture());\n    HttpException exception = (HttpException) exceptionArgumentCaptor.getValue();\n    assertThat(exception.getStatusCode()).isEqualTo(HttpURLConnection.HTTP_FORBIDDEN);\n  }\n\n  @Test\n  public void testRequestComplete_with200AndCancelled_callsCallbackWithNullException()\n      throws Exception {\n    UrlResponseInfo info = getInfo(0, 200);\n    fetcher.loadData(Priority.LOW, callback);\n    Callback urlCallback = urlRequestListenerCaptor.getValue();\n    urlCallback.onResponseStarted(request, info);\n    urlCallback.onCanceled(request, info);\n\n    verify(callback, timeout(1000)).onLoadFailed(ArgumentMatchers.<Exception>isNull());\n  }\n\n  @Test\n  public void testRequestComplete_with200NotCancelledMatchingLength_callsCallbackWithValidData()\n      throws Exception {\n    String data = \"data\";\n    ByteBuffer expected = ByteBuffer.wrap(data.getBytes());\n    ArgumentCaptor<ByteBuffer> captor = ArgumentCaptor.forClass(ByteBuffer.class);\n\n    fetcher.loadData(Priority.LOW, callback);\n    succeed(\n        getInfo(expected.remaining(), 200),\n        urlRequestListenerCaptor.getValue(),\n        expected.duplicate());\n\n    verify(callback, timeout(1000)).onDataReady(captor.capture());\n\n    ByteBuffer received = captor.getValue();\n\n    assertThat(\n            new String(\n                received.array(),\n                received.arrayOffset() + received.position(),\n                received.remaining()))\n        .isEqualTo(data);\n  }\n\n  private static Map<String, String> anyHeaders() {\n    return anyMap();\n  }\n}\n"
  },
  {
    "path": "integration/gifencoder/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.gifencoder\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    sourceSets {\n        getByName(\"main\") {\n            java.srcDirs(\"src/main/java\", \"../../third_party/gif_encoder/src/main/java\")\n        }\n    }\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n\n    testImplementation(project(\":testutil\"))\n    testImplementation(libs.truth)\n    testImplementation(libs.junit)\n    testImplementation(libs.mockito.core)\n    testImplementation(libs.robolectric)\n    testImplementation(libs.androidx.test.core)\n    testImplementation(libs.androidx.junit)\n    testImplementation(libs.androidx.test.runner)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/gifencoder/gradle.properties",
    "content": "POM_NAME=Glide GifEncoder Integration\nPOM_ARTIFACT_ID=gifencoder-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library allowing users to re-encode or create animated GIFs\n"
  },
  {
    "path": "integration/gifencoder/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n    <!-- See https://github.com/square/okio/issues/58 -->\n    <issue id=\"InvalidPackage\" severity=\"ignore\">\n        <ignore regexp=\"okio-1.0.0.jar\"/>\n    </issue>\n</lint>\n"
  },
  {
    "path": "integration/gifencoder/src/main/java/com/bumptech/glide/integration/gifencoder/ReEncodingGifResourceEncoder.java",
    "content": "package com.bumptech.glide.integration.gifencoder;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.gifdecoder.GifHeader;\nimport com.bumptech.glide.gifdecoder.GifHeaderParser;\nimport com.bumptech.glide.gifdecoder.StandardGifDecoder;\nimport com.bumptech.glide.gifencoder.AnimatedGifEncoder;\nimport com.bumptech.glide.load.EncodeStrategy;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.UnitTransformation;\nimport com.bumptech.glide.load.resource.bitmap.BitmapResource;\nimport com.bumptech.glide.load.resource.gif.GifBitmapProvider;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport com.bumptech.glide.util.LogTime;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\n\n/**\n * An {@link com.bumptech.glide.load.ResourceEncoder} that can write {@link\n * com.bumptech.glide.load.resource.gif.GifDrawable} to cache.\n */\npublic class ReEncodingGifResourceEncoder implements ResourceEncoder<GifDrawable> {\n\n  private static final String KEY_ENCODE_TRANSFORMATION =\n      \"com.bumptech.glide.load.resource.gif.GifResourceEncoder.EncodeTransformation\";\n\n  /**\n   * A boolean option that, if set to <code>true</code>, causes the fully transformed GIF to be\n   * written to cache.\n   *\n   * <p>Warning - encoding GIFs is slow and often produces larger and less efficient GIFs than the\n   * originals. Re-encoding may be worth it to decrease the size of very large GIFs.\n   *\n   * <p>Defaults to <code>false</code>.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static final Option<Boolean> ENCODE_TRANSFORMATION =\n      Option.disk(\n          KEY_ENCODE_TRANSFORMATION,\n          false,\n          new Option.CacheKeyUpdater<Boolean>() {\n            @Override\n            public void update(\n                @NonNull byte[] keyBytes,\n                @NonNull Boolean value,\n                @NonNull MessageDigest messageDigest) {\n              if (value) {\n                messageDigest.update(keyBytes);\n              }\n            }\n          });\n\n  private static final Factory FACTORY = new Factory();\n  private static final String TAG = \"GifEncoder\";\n  private final GifDecoder.BitmapProvider provider;\n  private final Context context;\n  private final BitmapPool bitmapPool;\n  private final Factory factory;\n\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public ReEncodingGifResourceEncoder(@NonNull Context context, @NonNull BitmapPool bitmapPool) {\n    this(context, bitmapPool, FACTORY);\n  }\n\n  @VisibleForTesting\n  ReEncodingGifResourceEncoder(Context context, BitmapPool bitmapPool, Factory factory) {\n    this.context = context;\n    this.bitmapPool = bitmapPool;\n    provider = new GifBitmapProvider(bitmapPool);\n    this.factory = factory;\n  }\n\n  @NonNull\n  @Override\n  public EncodeStrategy getEncodeStrategy(@NonNull Options options) {\n    Boolean encodeTransformation = options.get(ENCODE_TRANSFORMATION);\n    return encodeTransformation != null && encodeTransformation\n        ? EncodeStrategy.TRANSFORMED\n        : EncodeStrategy.SOURCE;\n  }\n\n  @Override\n  public boolean encode(\n      @NonNull Resource<GifDrawable> resource, @NonNull File file, @NonNull Options options) {\n    GifDrawable drawable = resource.get();\n    Transformation<Bitmap> transformation = drawable.getFrameTransformation();\n    boolean isTransformed = !(transformation instanceof UnitTransformation);\n    if (isTransformed && options.get(ENCODE_TRANSFORMATION)) {\n      return encodeTransformedToFile(drawable, file);\n    } else {\n      return writeDataDirect(drawable.getBuffer(), file);\n    }\n  }\n\n  private boolean encodeTransformedToFile(GifDrawable drawable, File file) {\n    long startTime = LogTime.getLogTime();\n    OutputStream os = null;\n    boolean success = false;\n    try {\n      os = new BufferedOutputStream(new FileOutputStream(file));\n      success = encodeTransformedToStream(drawable, os);\n      os.close();\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to encode GIF\", e);\n      }\n    } finally {\n      if (os != null) {\n        try {\n          os.close();\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n    }\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(\n          TAG,\n          \"Re-encoded GIF with \"\n              + drawable.getFrameCount()\n              + \" frames and \"\n              + drawable.getBuffer().limit()\n              + \" bytes in \"\n              + LogTime.getElapsedMillis(startTime)\n              + \" ms\");\n    }\n\n    return success;\n  }\n\n  private boolean encodeTransformedToStream(GifDrawable drawable, OutputStream os) {\n    Transformation<Bitmap> transformation = drawable.getFrameTransformation();\n    GifDecoder decoder = decodeHeaders(drawable.getBuffer());\n    AnimatedGifEncoder encoder = factory.buildEncoder();\n    if (!encoder.start(os)) {\n      return false;\n    }\n\n    for (int i = 0; i < decoder.getFrameCount(); i++) {\n      Bitmap currentFrame = decoder.getNextFrame();\n      Resource<Bitmap> transformedResource =\n          getTransformedFrame(currentFrame, transformation, drawable);\n      try {\n        if (!encoder.addFrame(transformedResource.get())) {\n          return false;\n        }\n        int currentFrameIndex = decoder.getCurrentFrameIndex();\n        int delay = decoder.getDelay(currentFrameIndex);\n        encoder.setDelay(delay);\n\n        decoder.advance();\n      } finally {\n        transformedResource.recycle();\n      }\n    }\n\n    return encoder.finish();\n  }\n\n  private boolean writeDataDirect(ByteBuffer data, File file) {\n    try {\n      ByteBufferUtil.toFile(data, file);\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Failed to write GIF data\", e);\n      }\n      return false;\n    }\n    return true;\n  }\n\n  private GifDecoder decodeHeaders(ByteBuffer data) {\n    GifHeaderParser parser = factory.buildParser();\n    parser.setData(data);\n    GifHeader header = parser.parseHeader();\n\n    GifDecoder decoder = factory.buildDecoder(provider);\n    decoder.setData(header, data);\n    decoder.advance();\n\n    return decoder;\n  }\n\n  private Resource<Bitmap> getTransformedFrame(\n      Bitmap currentFrame, Transformation<Bitmap> transformation, GifDrawable drawable) {\n    // TODO: what if current frame is null?\n    Resource<Bitmap> bitmapResource = factory.buildFrameResource(currentFrame, bitmapPool);\n    Resource<Bitmap> transformedResource =\n        transformation.transform(\n            context, bitmapResource, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());\n    if (!bitmapResource.equals(transformedResource)) {\n      bitmapResource.recycle();\n    }\n    return transformedResource;\n  }\n\n  @VisibleForTesting\n  static class Factory {\n\n    GifDecoder buildDecoder(GifDecoder.BitmapProvider bitmapProvider) {\n      return new StandardGifDecoder(bitmapProvider);\n    }\n\n    GifHeaderParser buildParser() {\n      return new GifHeaderParser();\n    }\n\n    AnimatedGifEncoder buildEncoder() {\n      return new AnimatedGifEncoder();\n    }\n\n    @NonNull\n    Resource<Bitmap> buildFrameResource(@NonNull Bitmap bitmap, @NonNull BitmapPool bitmapPool) {\n      return new BitmapResource(bitmap, bitmapPool);\n    }\n  }\n}\n"
  },
  {
    "path": "integration/gifencoder/src/test/java/com/bumptech/glide/integration/gifencoder/ReEncodingGifResourceEncoderTest.java",
    "content": "package com.bumptech.glide.integration.gifencoder;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.gifdecoder.GifHeader;\nimport com.bumptech.glide.gifdecoder.GifHeaderParser;\nimport com.bumptech.glide.gifencoder.AnimatedGifEncoder;\nimport com.bumptech.glide.load.EncodeStrategy;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.UnitTransformation;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.ByteBuffer;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n/** Tests for {@link com.bumptech.glide.integration.gifencoder.ReEncodingGifResourceEncoder}. */\n@RunWith(RobolectricTestRunner.class)\n@Config(manifest = Config.NONE, sdk = 19)\npublic class ReEncodingGifResourceEncoderTest {\n  @Mock private Resource<GifDrawable> resource;\n  @Mock private GifDecoder decoder;\n  @Mock private GifHeaderParser parser;\n  @Mock private AnimatedGifEncoder gifEncoder;\n  @Mock private Resource<Bitmap> frameResource;\n  @Mock private GifDrawable gifDrawable;\n  @Mock private Transformation<Bitmap> frameTransformation;\n  @Mock private Resource<Bitmap> transformedResource;\n\n  private ReEncodingGifResourceEncoder encoder;\n  private Options options;\n  private File file;\n\n  @SuppressWarnings(\"unchecked\")\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    Application context = ApplicationProvider.getApplicationContext();\n\n    ReEncodingGifResourceEncoder.Factory factory = mock(ReEncodingGifResourceEncoder.Factory.class);\n    when(decoder.getNextFrame()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    when(factory.buildDecoder(any(GifDecoder.BitmapProvider.class))).thenReturn(decoder);\n    when(factory.buildParser()).thenReturn(parser);\n    when(factory.buildEncoder()).thenReturn(gifEncoder);\n    when(factory.buildFrameResource(anyBitmapOrNull(), any(BitmapPool.class)))\n        .thenReturn(frameResource);\n\n    // TODO Util.anyResource once Util is moved to testutil module (remove unchecked above!)\n    when(frameTransformation.transform(anyContext(), any(Resource.class), anyInt(), anyInt()))\n        .thenReturn(frameResource);\n\n    when(gifDrawable.getFrameTransformation()).thenReturn(frameTransformation);\n    when(gifDrawable.getBuffer()).thenReturn(ByteBuffer.allocate(0));\n\n    when(resource.get()).thenReturn(gifDrawable);\n\n    encoder = new ReEncodingGifResourceEncoder(context, mock(BitmapPool.class), factory);\n    options = new Options();\n    options.set(ReEncodingGifResourceEncoder.ENCODE_TRANSFORMATION, true);\n\n    file = new File(context.getCacheDir(), \"test\");\n  }\n\n  @After\n  public void tearDown() {\n    // GC before delete() to release files on Windows (https://stackoverflow.com/a/4213208/253468)\n    System.gc();\n    if (file.exists() && !file.delete()) {\n      throw new RuntimeException(\"Failed to delete file\");\n    }\n  }\n\n  @Test\n  public void testEncodeStrategy_withEncodeTransformationTrue_returnsTransformed() {\n    assertThat(encoder.getEncodeStrategy(options)).isEqualTo(EncodeStrategy.TRANSFORMED);\n  }\n\n  @Test\n  public void testEncodeStrategy_withEncodeTransformationUnSet_returnsSource() {\n    options.set(ReEncodingGifResourceEncoder.ENCODE_TRANSFORMATION, null);\n    assertThat(encoder.getEncodeStrategy(options)).isEqualTo(EncodeStrategy.SOURCE);\n  }\n\n  @Test\n  public void testEncodeStrategy_withEncodeTransformationFalse_returnsSource() {\n    options.set(ReEncodingGifResourceEncoder.ENCODE_TRANSFORMATION, false);\n    assertThat(encoder.getEncodeStrategy(options)).isEqualTo(EncodeStrategy.SOURCE);\n  }\n\n  @Test\n  public void testEncode_withEncodeTransformationFalse_writesSourceDataToStream()\n      throws IOException {\n    options.set(ReEncodingGifResourceEncoder.ENCODE_TRANSFORMATION, false);\n    String expected = \"testString\";\n    byte[] data = expected.getBytes(\"UTF-8\");\n    when(gifDrawable.getBuffer()).thenReturn(ByteBuffer.wrap(data));\n\n    assertTrue(encoder.encode(resource, file, options));\n    assertThat(getEncodedData()).isEqualTo(expected);\n  }\n\n  @Test\n  public void testEncode_WithEncodeTransformationFalse_whenOsThrows_returnsFalse()\n      throws IOException {\n    options.set(ReEncodingGifResourceEncoder.ENCODE_TRANSFORMATION, false);\n    byte[] data = \"testString\".getBytes(\"UTF-8\");\n    when(gifDrawable.getBuffer()).thenReturn(ByteBuffer.wrap(data));\n\n    assertThat(file.mkdirs()).isTrue();\n\n    assertFalse(encoder.encode(resource, file, options));\n  }\n\n  @Test\n  public void testReturnsFalseIfEncoderFailsToStart() {\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(false);\n    assertFalse(encoder.encode(resource, file, options));\n  }\n\n  @Test\n  public void testSetsDataOnParserBeforeParsingHeader() {\n    ByteBuffer data = ByteBuffer.allocate(1);\n    when(gifDrawable.getBuffer()).thenReturn(data);\n\n    GifHeader header = mock(GifHeader.class);\n    when(parser.parseHeader()).thenReturn(header);\n\n    encoder.encode(resource, file, options);\n\n    InOrder order = inOrder(parser, decoder);\n    order.verify(parser).setData(eq(data));\n    order.verify(parser).parseHeader();\n    order.verify(decoder).setData(header, data);\n  }\n\n  @Test\n  public void testAdvancesDecoderBeforeAttemptingToGetFirstFrame() {\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n    when(decoder.getFrameCount()).thenReturn(1);\n    when(decoder.getNextFrame()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n\n    encoder.encode(resource, file, options);\n\n    InOrder order = inOrder(decoder);\n    order.verify(decoder).advance();\n    order.verify(decoder).getNextFrame();\n  }\n\n  @Test\n  public void testSetsDelayOnEncoderAfterAddingFrame() {\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n    when(gifEncoder.addFrame(anyBitmapOrNull())).thenReturn(true);\n\n    when(decoder.getFrameCount()).thenReturn(1);\n    when(decoder.getNextFrame()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565));\n    int expectedIndex = 34;\n    when(decoder.getCurrentFrameIndex()).thenReturn(expectedIndex);\n    int expectedDelay = 5000;\n    when(decoder.getDelay(eq(expectedIndex))).thenReturn(expectedDelay);\n\n    encoder.encode(resource, file, options);\n\n    InOrder order = inOrder(gifEncoder, decoder);\n    order.verify(decoder).advance();\n    order.verify(gifEncoder).addFrame(anyBitmapOrNull());\n    order.verify(gifEncoder).setDelay(eq(expectedDelay));\n    order.verify(decoder).advance();\n  }\n\n  @Test\n  public void testWritesSingleFrameToEncoderAndReturnsTrueIfEncoderFinishes() {\n    Bitmap frame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(frameResource.get()).thenReturn(frame);\n\n    when(decoder.getFrameCount()).thenReturn(1);\n    when(decoder.getNextFrame()).thenReturn(frame);\n\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n    when(gifEncoder.addFrame(eq(frame))).thenReturn(true);\n    when(gifEncoder.finish()).thenReturn(true);\n\n    assertTrue(encoder.encode(resource, file, options));\n    verify(gifEncoder).addFrame(eq(frame));\n  }\n\n  @Test\n  public void testReturnsFalseIfAddingFrameFails() {\n    when(decoder.getFrameCount()).thenReturn(1);\n    when(decoder.getNextFrame()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n    when(gifEncoder.addFrame(anyBitmapOrNull())).thenReturn(false);\n\n    assertFalse(encoder.encode(resource, file, options));\n  }\n\n  @Test\n  public void testReturnsFalseIfFinishingFails() {\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n    when(gifEncoder.finish()).thenReturn(false);\n\n    assertFalse(encoder.encode(resource, file, options));\n  }\n\n  @Test\n  public void testWritesTransformedBitmaps() {\n    final Bitmap frame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(decoder.getFrameCount()).thenReturn(1);\n    when(decoder.getNextFrame()).thenReturn(frame);\n\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n\n    int expectedWidth = 123;\n    int expectedHeight = 456;\n    when(gifDrawable.getIntrinsicWidth()).thenReturn(expectedWidth);\n    when(gifDrawable.getIntrinsicHeight()).thenReturn(expectedHeight);\n\n    Bitmap transformedFrame = Bitmap.createBitmap(200, 200, Bitmap.Config.RGB_565);\n    when(transformedResource.get()).thenReturn(transformedFrame);\n    when(frameTransformation.transform(\n            anyContext(), eq(frameResource), eq(expectedWidth), eq(expectedHeight)))\n        .thenReturn(transformedResource);\n    when(gifDrawable.getFrameTransformation()).thenReturn(frameTransformation);\n\n    encoder.encode(resource, file, options);\n\n    verify(gifEncoder).addFrame(eq(transformedFrame));\n  }\n\n  @Test\n  public void testRecyclesFrameResourceBeforeWritingIfTransformedResourceIsDifferent() {\n    when(decoder.getFrameCount()).thenReturn(1);\n    when(frameTransformation.transform(anyContext(), eq(frameResource), anyInt(), anyInt()))\n        .thenReturn(transformedResource);\n    Bitmap expected = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);\n    when(transformedResource.get()).thenReturn(expected);\n\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n\n    encoder.encode(resource, file, options);\n\n    InOrder order = inOrder(frameResource, gifEncoder);\n    order.verify(frameResource).recycle();\n    order.verify(gifEncoder).addFrame(eq(expected));\n  }\n\n  @Test\n  public void testRecyclesTransformedResourceAfterWritingIfTransformedResourceIsDifferent() {\n    when(decoder.getFrameCount()).thenReturn(1);\n    Bitmap expected = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);\n    when(transformedResource.get()).thenReturn(expected);\n    when(frameTransformation.transform(anyContext(), eq(frameResource), anyInt(), anyInt()))\n        .thenReturn(transformedResource);\n\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n\n    encoder.encode(resource, file, options);\n\n    InOrder order = inOrder(transformedResource, gifEncoder);\n    order.verify(gifEncoder).addFrame(eq(expected));\n    order.verify(transformedResource).recycle();\n  }\n\n  @Test\n  public void testRecyclesFrameResourceAfterWritingIfFrameResourceIsNotTransformed() {\n    when(decoder.getFrameCount()).thenReturn(1);\n    when(frameTransformation.transform(anyContext(), eq(frameResource), anyInt(), anyInt()))\n        .thenReturn(frameResource);\n    Bitmap expected = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888);\n    when(frameResource.get()).thenReturn(expected);\n\n    when(gifEncoder.start(any(OutputStream.class))).thenReturn(true);\n\n    encoder.encode(resource, file, options);\n\n    InOrder order = inOrder(frameResource, gifEncoder);\n    order.verify(gifEncoder).addFrame(eq(expected));\n    order.verify(frameResource).recycle();\n  }\n\n  @Test\n  public void testWritesBytesDirectlyToDiskIfTransformationIsUnitTransformation() {\n    when(gifDrawable.getFrameTransformation()).thenReturn(UnitTransformation.<Bitmap>get());\n    String expected = \"expected\";\n    when(gifDrawable.getBuffer()).thenReturn(ByteBuffer.wrap(expected.getBytes()));\n\n    encoder.encode(resource, file, options);\n\n    assertThat(getEncodedData()).isEqualTo(expected);\n\n    verify(gifEncoder, never()).start(any(OutputStream.class));\n    verify(parser, never()).setData(any(byte[].class));\n    verify(parser, never()).parseHeader();\n  }\n\n  private String getEncodedData() {\n    try {\n      return new String(ByteBufferUtil.toBytes(ByteBufferUtil.fromFile(file)));\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private static Context anyContext() {\n    return any(Context.class);\n  }\n\n  private static Bitmap anyBitmapOrNull() {\n    return any();\n  }\n}\n"
  },
  {
    "path": "integration/gradle.properties",
    "content": "# Prefix and postfix for source and javadoc jars.\nJAR_PREFIX=glide-\nJAR_POSTFIX=-integration\n"
  },
  {
    "path": "integration/ktx/api/ktx.api",
    "content": "public abstract interface annotation class com/bumptech/glide/integration/ktx/ExperimentGlideFlows : java/lang/annotation/Annotation {\n}\n\npublic final class com/bumptech/glide/integration/ktx/FlowsKt {\n\tpublic static final fun flow (Lcom/bumptech/glide/RequestBuilder;)Lkotlinx/coroutines/flow/Flow;\n\tpublic static final fun flow (Lcom/bumptech/glide/RequestBuilder;I)Lkotlinx/coroutines/flow/Flow;\n\tpublic static final fun flow (Lcom/bumptech/glide/RequestBuilder;II)Lkotlinx/coroutines/flow/Flow;\n}\n\npublic abstract class com/bumptech/glide/integration/ktx/GlideFlowInstant {\n\tpublic abstract fun getStatus ()Lcom/bumptech/glide/integration/ktx/Status;\n}\n\npublic abstract interface annotation class com/bumptech/glide/integration/ktx/InternalGlideApi : java/lang/annotation/Annotation {\n}\n\npublic final class com/bumptech/glide/integration/ktx/Placeholder : com/bumptech/glide/integration/ktx/GlideFlowInstant {\n\tpublic fun <init> (Lcom/bumptech/glide/integration/ktx/Status;Landroid/graphics/drawable/Drawable;)V\n\tpublic final fun component1 ()Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic final fun component2 ()Landroid/graphics/drawable/Drawable;\n\tpublic final fun copy (Lcom/bumptech/glide/integration/ktx/Status;Landroid/graphics/drawable/Drawable;)Lcom/bumptech/glide/integration/ktx/Placeholder;\n\tpublic static synthetic fun copy$default (Lcom/bumptech/glide/integration/ktx/Placeholder;Lcom/bumptech/glide/integration/ktx/Status;Landroid/graphics/drawable/Drawable;ILjava/lang/Object;)Lcom/bumptech/glide/integration/ktx/Placeholder;\n\tpublic fun equals (Ljava/lang/Object;)Z\n\tpublic final fun getPlaceholder ()Landroid/graphics/drawable/Drawable;\n\tpublic fun getStatus ()Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic fun hashCode ()I\n\tpublic fun toString ()Ljava/lang/String;\n}\n\npublic final class com/bumptech/glide/integration/ktx/Resource : com/bumptech/glide/integration/ktx/GlideFlowInstant {\n\tpublic fun <init> (Lcom/bumptech/glide/integration/ktx/Status;Ljava/lang/Object;)V\n\tpublic final fun component1 ()Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic final fun component2 ()Ljava/lang/Object;\n\tpublic final fun copy (Lcom/bumptech/glide/integration/ktx/Status;Ljava/lang/Object;)Lcom/bumptech/glide/integration/ktx/Resource;\n\tpublic static synthetic fun copy$default (Lcom/bumptech/glide/integration/ktx/Resource;Lcom/bumptech/glide/integration/ktx/Status;Ljava/lang/Object;ILjava/lang/Object;)Lcom/bumptech/glide/integration/ktx/Resource;\n\tpublic fun equals (Ljava/lang/Object;)Z\n\tpublic final fun getResource ()Ljava/lang/Object;\n\tpublic fun getStatus ()Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic fun hashCode ()I\n\tpublic fun toString ()Ljava/lang/String;\n}\n\npublic final class com/bumptech/glide/integration/ktx/Status : java/lang/Enum {\n\tpublic static final field CLEARED Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic static final field FAILED Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic static final field RUNNING Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic static final field SUCCEEDED Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic static fun getEntries ()Lkotlin/enums/EnumEntries;\n\tpublic static fun valueOf (Ljava/lang/String;)Lcom/bumptech/glide/integration/ktx/Status;\n\tpublic static fun values ()[Lcom/bumptech/glide/integration/ktx/Status;\n}\n\n"
  },
  {
    "path": "integration/ktx/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\nplugins {\n    id(\"com.android.library\")\n    id(\"kotlin-android\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.ktx\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes { getByName(\"release\") { isMinifyEnabled = false } }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n\n    kotlinOptions { jvmTarget = \"1.8\" }\n}\n\n// Enable strict mode, but exclude tests.\ntasks.withType(KotlinCompile::class.java).configureEach {\n    if (!name.contains(\"Test\")) {\n        kotlinOptions.freeCompilerArgs += \"-Xexplicit-api=strict\"\n    }\n}\n\ndependencies {\n    api(project(\":library\"))\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.coroutines.core)\n\n    testImplementation(libs.androidx.espresso)\n    testImplementation(libs.androidx.espresso.idling)\n    testImplementation(libs.androidx.test.ktx)\n    testImplementation(libs.kotlin.junit)\n    testImplementation(libs.androidx.test.ktx.junit)\n    testImplementation(libs.androidx.junit)\n    testImplementation(libs.robolectric)\n    testImplementation(libs.androidx.test.runner)\n    testImplementation(libs.junit)\n    testImplementation(libs.coroutines.test)\n    testImplementation(libs.truth)\n\n    androidTestImplementation(libs.androidx.junit)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")\n"
  },
  {
    "path": "integration/ktx/gradle.properties",
    "content": "POM_NAME=Glide Kotlin Extensions\nPOM_ARTIFACT_ID=ktx\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to improve Kotlin interop with Glide\n\nVERSION_MAJOR=1\nVERSION_MINOR=0\nVERSION_PATCH=0\nVERSION_NAME=1.0.0-beta08"
  },
  {
    "path": "integration/ktx/src/main/java/com/bumptech/glide/GlideIntegration.kt",
    "content": "/**\n * Functions that give us access to some of Glide's non-public internals to make the flows API a bit\n * better.\n */\npackage com.bumptech.glide\n\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.target.Target\n\ninternal fun RequestBuilder<*>.requestManager() = this.requestManager\n\ninternal fun <ResourceT, TargetAndRequestListenerT> RequestBuilder<ResourceT>.intoDirect(\n    targetAndRequestListener: TargetAndRequestListenerT\n)\n    where\n        TargetAndRequestListenerT : Target<ResourceT>,\n        TargetAndRequestListenerT : RequestListener<ResourceT> {\n    this.into(targetAndRequestListener, targetAndRequestListener) { it.run() }\n}\n"
  },
  {
    "path": "integration/ktx/src/main/java/com/bumptech/glide/integration/ktx/Flows.kt",
    "content": "package com.bumptech.glide.integration.ktx\n\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.GuardedBy\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.intoDirect\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.Request\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.target.SizeReadyCallback\nimport com.bumptech.glide.request.target.Target\nimport com.bumptech.glide.request.transition.Transition\nimport com.bumptech.glide.requestManager\nimport com.bumptech.glide.util.Util\nimport kotlinx.coroutines.channels.ProducerScope\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.launch\n\n@RequiresOptIn(\n    level = RequiresOptIn.Level.ERROR,\n    message =\n        \"Glide's flow integration is very experimental and subject to breaking API or behavior changes\",\n)\n@Retention(AnnotationRetention.BINARY)\n@kotlin.annotation.Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)\npublic annotation class ExperimentGlideFlows\n\n/**\n * The current status of a flow\n *\n * There is no well established graph that defines the valid Status transitions. Depending on\n * various factors like the parameters of the request, whether or not the resource is in the memory\n * cache, or even various calls to Glide's APIs, these may be emitted in different orders. As an\n * example, [RUNNING] is skipped if a request can be immediately completed from the memory cache.\n *\n * See [flow] for more details.\n */\n@ExperimentGlideFlows\npublic enum class Status {\n    /** The load is not started or has been cleared. */\n    CLEARED,\n    /** At least the primary load is still in progress. */\n    RUNNING,\n    /**\n     * The primary load or the error load ([RequestBuilder.error]) associated with the primary have\n     * finished successfully.\n     */\n    SUCCEEDED,\n    /** The primary load has failed. One or more thumbnails may have succeeded. */\n    FAILED,\n}\n\n/**\n * Identical to [flow] with [Target.SIZE_ORIGINAL] as the dimensions\n *\n * This isn't generally a good idea, [Target.SIZE_ORIGINAL] is often much larger than you need.\n * Using it unnecessarily will waste memory and cache space. It will also slow down future loads\n * from the disk cache.\n *\n * Use this method only if you you expect the request and all of the subrequests (\n * [RequestBuilder.override] and [RequestBuilder.error] to have specific sizes set). Validation is\n * only performed on the top level request because we cannot reliably verify all possible\n * subrequests.\n */\n@ExperimentGlideFlows\npublic fun <ResourceT : Any> RequestBuilder<ResourceT>.flow(): Flow<GlideFlowInstant<ResourceT>> {\n    require(isValidOverride) {\n        \"At least your primary request is missing override dimensions. If you want to use\" +\n            \" Target.SIZE_ORIGINAL, do so explicitly\"\n    }\n    return flow(Target.SIZE_ORIGINAL)\n}\n\n/** Identical to `flow(dimension, dimension)` */\n@ExperimentGlideFlows\npublic fun <ResourceT : Any> RequestBuilder<ResourceT>.flow(\n    dimension: Int\n): Flow<GlideFlowInstant<ResourceT>> = flow(dimension, dimension)\n\n/**\n * Identical to [flow] with dimensions, except that the size is resolved asynchronously using\n * [waitForSize].\n *\n * If an override size has been set using [RequestBuilder.override], that size will be used instead\n * and [waitForSize] may never be called.\n *\n * [Placeholder] values may be emitted prior to [waitForSize] returning. Similarly if\n * [RequestBuilder.thumbnail] requests are present and have overridden sizes, [Resource] values for\n * those thumbnails may also be emitted. [waitForSize] will only be used for requests where no\n * [RequestBuilder.override] size is available.\n *\n * If [waitForSize] does not return, this flow may never return values other than placeholders.\n *\n * This function is internal only, intended primarily for Compose. The Target API provides similar\n * functionality for traditional Views. We could consider expanding the visibility if there are use\n * cases for asynchronous size resolution outside of Glide's Compose integration.\n */\n@InternalGlideApi\n@ExperimentGlideFlows\npublic fun <ResourceT : Any> RequestBuilder<ResourceT>.flow(\n    waitForSize: suspend () -> Size\n): Flow<GlideFlowInstant<ResourceT>> = flow(AsyncGlideSize(waitForSize))\n\n/**\n * Convert a load in Glide into a flow that emits placeholders and resources in the order they'd be\n * seen by a [Target].\n *\n * Just like a [Target] there is no well defined end to a Glide request. Barring cancellation, the\n * flow should eventually reach [Status.SUCCEEDED] or [Status.FAILED] at least once. However\n * connectivity changes, calls to [com.bumptech.glide.RequestManager.pauseAllRequests] or\n * [com.bumptech.glide.RequestManager.resumeRequests], or the lifecycle associated with this request\n * may cause the request to be started multiple times. As long as the flow is active, callers will\n * receive emissions from every run.\n *\n * This flow will clear the associated Glide request when it's cancelled. This means that callers\n * must keep the flow active while any resource emitted by the flow is still in use. For UI\n * contexts, collecting the flow in the appropriate fragment or view model coroutine context is\n * sufficient as long as you avoid truncating methods like [kotlinx.coroutines.flow.take],\n * [kotlinx.coroutines.flow.takeWhile], etc. If you do use these methods, you must be sure that\n * you're no longer using or displaying the associated resource once the flow is no longer active\n * (ie [kotlinx.coroutines.flow.collect] finishes). One way to do this would be to mimic the UI by\n * creating and keeping active a coroutine context that collects from the flow while the resource is\n * in use. If this restriction is limiting for you, please file an issue on Github so we can think\n * of alternative options.\n *\n * If there have been any previous calls to this [RequestBuilder]'s\n * [com.bumptech.glide.request.RequestOptions.override] method, the size specified in that method\n * will be used instead of the size provided here. This includes calls where override sizes may have\n * been copied from other option sets via [RequestBuilder.apply].\n */\n@ExperimentGlideFlows\n@OptIn(InternalGlideApi::class)\npublic fun <ResourceT : Any> RequestBuilder<ResourceT>.flow(\n    width: Int,\n    height: Int,\n): Flow<GlideFlowInstant<ResourceT>> {\n    require(Util.isValidDimensions(width, height))\n    return flow(Size(width = width, height = height))\n}\n\n// We're not asserting on size here because it might come from RequestBuilder.override. Assertions\n// for provided sizes belong in those methods, assertions for overrides belong in the override\n// method.\n@InternalGlideApi\n@ExperimentGlideFlows\nprivate fun <ResourceT : Any> RequestBuilder<ResourceT>.flow(\n    size: Size\n): Flow<GlideFlowInstant<ResourceT>> = flowResolvable(ImmediateGlideSize(size))\n\n@OptIn(ExperimentGlideFlows::class)\n@InternalGlideApi\npublic fun <ResourceT : Any> RequestBuilder<ResourceT>.flowResolvable(\n    size: ResolvableGlideSize\n): Flow<GlideFlowInstant<ResourceT>> = flow(size)\n\n/**\n * A [Status] and value pair, where the value is either a [Placeholder] or a [Resource] depending on\n * how far the Glide load has progressed and/or how successful it's been.\n */\n@ExperimentGlideFlows\npublic sealed class GlideFlowInstant<ResourceT> {\n    public abstract val status: Status\n}\n\n/**\n * Wraps a [Status] and a placeholder [Drawable] (from [RequestBuilder.placeholder],\n * [RequestBuilder.fallback], [RequestBuilder.error] etc).\n */\n@ExperimentGlideFlows\npublic data class Placeholder<ResourceT>(\n    public override val status: Status,\n    public val placeholder: Drawable?,\n) : GlideFlowInstant<ResourceT>() {\n    init {\n        require(\n            when (status) {\n                Status.SUCCEEDED -> false\n                Status.CLEARED -> true\n                // Placeholder will be present prior to the first thumbnail succeeding\n                Status.RUNNING -> true\n                Status.FAILED -> true\n            }\n        )\n    }\n}\n\n/**\n * Wraps a [Status] and a resource loaded from the primary request, a [RequestBuilder.thumbnail]\n * request, or a [RequestBuilder.error] request.\n *\n * **Status.FAILED** is a perfectly valid status with this class. If the primary request fails, but\n * at least one thumbnail succeeds, the flow will emit `Resource(FAILED, resource)` to indicate both\n * that we have some value but also that the primary request has failed.\n */\n@ExperimentGlideFlows\npublic data class Resource<ResourceT>(\n    public override val status: Status,\n    public val resource: ResourceT,\n) : GlideFlowInstant<ResourceT>() {\n    init {\n        require(\n            when (status) {\n                Status.SUCCEEDED -> true\n                // A load with thumbnail(s) where the thumbnail(s) have finished but not the main\n                // request\n                Status.RUNNING -> true\n                // The primary request of the load failed, but at least one thumbnail was\n                // successful.\n                Status.FAILED -> true\n                // Once the load is cleared, it can only show a placeholder\n                Status.CLEARED -> false\n            }\n        )\n    }\n}\n\n@InternalGlideApi\n@ExperimentGlideFlows\nprivate fun <ResourceT : Any> RequestBuilder<ResourceT>.flow(\n    size: ResolvableGlideSize\n): Flow<GlideFlowInstant<ResourceT>> {\n    val requestBuilder = this\n    val requestManager = requestBuilder.requestManager()\n    return callbackFlow {\n        val target = FlowTarget(this, size)\n        requestBuilder.intoDirect(target)\n        awaitClose { requestManager.clear(target) }\n    }\n}\n\n/**\n * Observes a glide request using [Target] and [RequestListener] and tries to emit something\n * resembling a coherent set of placeholders and resources for it.\n *\n * Threading in this class is a bit complicated. As a general rule, the callback methods are ordered\n * by callers. So we have to handle being called from multiple threads, but we don't need to try to\n * handle callbacks being called in parallel.\n *\n * The primary area of concern around thread is that [resolvedSize] and [sizeReadyCallbacks] must be\n * updated atomically, but can be modified on different threads.\n *\n * [currentRequest] would normally be a concern because [Target]s can be cancelled on threads other\n * than where they were started. However in our case, [currentRequest] is set once when our request\n * is started (by us) and is only cancelled when the request finishes. So we just have to avoid NPEs\n * and make sure the state is reasonably up to date.\n *\n * [lastResource] is an unfortunate hack that tries to make sure that we emit [Status.FAILED] if a\n * thumbnail request succeeds, but then the primary request fails. In that case, we'd normally\n * already have emitted [Resource] with [Status.RUNNING] and the thumbnail value and then we'd emit\n * nothing else. That's not very satisfying for callers who expect some resolution. So instead we\n * track the last resource produced by thumbnails and emit that along with [Status.FAILED] when we\n * see that the primary request has failed. As a result we're not concerned with ordering with\n * regards to [lastResource], but it is possible the callbacks will be called on different threads,\n * so the value may be updated from different threads even if it's not concurrent.\n */\n@ExperimentGlideFlows\n@InternalGlideApi\nprivate class FlowTarget<ResourceT : Any>(\n    private val scope: ProducerScope<GlideFlowInstant<ResourceT>>,\n    private val size: ResolvableGlideSize,\n) : Target<ResourceT>, RequestListener<ResourceT> {\n    @Volatile private var resolvedSize: Size? = null\n    @Volatile private var currentRequest: Request? = null\n    @Volatile private var lastResource: ResourceT? = null\n\n    @GuardedBy(\"this\") private val sizeReadyCallbacks = mutableListOf<SizeReadyCallback>()\n\n    init {\n        when (size) {\n            // If we have a size, skip the coroutine, we can continue immediately.\n            is ImmediateGlideSize -> resolvedSize = size.size\n            // Otherwise, we do not want to block the flow while waiting on a size because one or\n            // more\n            // requests in the chain may have a fixed size, even if the primary request does not.\n            // Starting the Glide request right away allows any subrequest that has a fixed size to\n            // begin immediately, shaving off some small amount of time.\n            is AsyncGlideSize ->\n                scope.launch {\n                    val localResolvedSize = size.asyncSize()\n                    val callbacksToNotify: List<SizeReadyCallback>\n                    synchronized(this) {\n                        resolvedSize = localResolvedSize\n                        callbacksToNotify = ArrayList(sizeReadyCallbacks)\n                        sizeReadyCallbacks.clear()\n                    }\n                    callbacksToNotify.forEach {\n                        it.onSizeReady(localResolvedSize.width, localResolvedSize.height)\n                    }\n                }\n        }\n    }\n\n    override fun onStart() {}\n\n    override fun onStop() {}\n\n    override fun onDestroy() {}\n\n    override fun onLoadStarted(placeholder: Drawable?) {\n        lastResource = null\n        scope.trySend(Placeholder(Status.RUNNING, placeholder))\n    }\n\n    override fun onLoadFailed(errorDrawable: Drawable?) {\n        scope.trySend(Placeholder(Status.FAILED, errorDrawable))\n    }\n\n    override fun onResourceReady(resource: ResourceT, transition: Transition<in ResourceT>?) {\n        lastResource = resource\n        scope.trySend(\n            Resource(\n                // currentRequest is the entire request state, so we can use it to figure out if\n                // this\n                // resource is from a thumbnail request (isComplete is false) or the primary\n                // request.\n                if (currentRequest?.isComplete == true) Status.SUCCEEDED else Status.RUNNING,\n                resource,\n            )\n        )\n    }\n\n    override fun onLoadCleared(placeholder: Drawable?) {\n        lastResource = null\n        scope.trySend(Placeholder(Status.CLEARED, placeholder))\n    }\n\n    override fun getSize(cb: SizeReadyCallback) {\n        val localResolvedSize = resolvedSize\n        if (localResolvedSize != null) {\n            cb.onSizeReady(localResolvedSize.width, localResolvedSize.height)\n            return\n        }\n\n        synchronized(this@FlowTarget) {\n            val lockedResolvedSize = resolvedSize\n            if (lockedResolvedSize != null) {\n                cb.onSizeReady(lockedResolvedSize.width, lockedResolvedSize.height)\n            } else {\n                sizeReadyCallbacks.add(cb)\n            }\n        }\n    }\n\n    override fun removeCallback(cb: SizeReadyCallback) {\n        synchronized(this) { sizeReadyCallbacks.remove(cb) }\n    }\n\n    override fun setRequest(request: Request?) {\n        currentRequest = request\n    }\n\n    override fun getRequest(): Request? {\n        return currentRequest\n    }\n\n    override fun onLoadFailed(\n        e: GlideException?,\n        model: Any?,\n        target: Target<ResourceT>?,\n        isFirstResource: Boolean,\n    ): Boolean {\n        val localLastResource = lastResource\n        val localRequest = currentRequest\n        if (\n            localLastResource != null &&\n                localRequest?.isComplete == false &&\n                !localRequest.isRunning\n        ) {\n            scope.channel.trySend(Resource(Status.FAILED, localLastResource))\n        }\n        return false\n    }\n\n    override fun onResourceReady(\n        resource: ResourceT,\n        model: Any?,\n        target: Target<ResourceT>?,\n        dataSource: DataSource?,\n        isFirstResource: Boolean,\n    ): Boolean {\n        return false\n    }\n}\n\n@InternalGlideApi\npublic data class Size(val width: Int, val height: Int) {\n    init {\n        require(width.isValidGlideDimension())\n        require(height.isValidGlideDimension())\n    }\n}\n\n@InternalGlideApi public sealed class ResolvableGlideSize\n\n@InternalGlideApi public data class ImmediateGlideSize(val size: Size) : ResolvableGlideSize()\n\n@InternalGlideApi\npublic data class AsyncGlideSize(val asyncSize: suspend () -> Size) : ResolvableGlideSize()\n\n@InternalGlideApi public fun Int.isValidGlideDimension(): Boolean = Util.isValidDimension(this)\n"
  },
  {
    "path": "integration/ktx/src/main/java/com/bumptech/glide/integration/ktx/InternalGlideApi.kt",
    "content": "package com.bumptech.glide.integration.ktx\n\n@RequiresOptIn(\n    level = RequiresOptIn.Level.ERROR,\n    message =\n        \"An internal only API not intended for public use, may change, break or be removed\" +\n            \" at any time without warning.\",\n)\n@Retention(AnnotationRetention.BINARY)\n@kotlin.annotation.Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)\npublic annotation class InternalGlideApi\n"
  },
  {
    "path": "integration/ktx/src/test/java/com/bumptech/glide/integration/ktx/FlowsTest.kt",
    "content": "@file:OptIn(InternalGlideApi::class, ExperimentGlideFlows::class, ExperimentalCoroutinesApi::class)\n\npackage com.bumptech.glide.integration.ktx\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport android.net.Uri\nimport androidx.test.core.app.ApplicationProvider\nimport androidx.test.espresso.Espresso.onIdle\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.GlideBuilder\nimport com.bumptech.glide.RequestManager\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.Key\nimport com.bumptech.glide.load.Options\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.load.engine.cache.MemoryCache\nimport com.bumptech.glide.load.engine.executor.GlideExecutor\nimport com.bumptech.glide.load.engine.executor.GlideIdlingResources\nimport com.bumptech.glide.load.model.ModelLoader\nimport com.bumptech.glide.load.model.ModelLoaderFactory\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.target.Target\nimport com.google.common.truth.Correspondence\nimport com.google.common.truth.IterableSubject\nimport com.google.common.truth.Truth.assertThat\nimport java.io.File\nimport java.lang.RuntimeException\nimport java.util.concurrent.atomic.AtomicReference\nimport kotlin.reflect.KClass\nimport kotlin.test.assertFailsWith\nimport kotlinx.coroutines.DelicateCoroutinesApi\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.take\nimport kotlinx.coroutines.flow.takeWhile\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.newSingleThreadContext\nimport kotlinx.coroutines.test.runTest\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.rules.TemporaryFolder\nimport org.junit.runner.RunWith\n\n// newFile throws IOException, which triggers this warning even though there's no reasonable\n// alternative :/.\n@Suppress(\"BlockingMethodInNonBlockingContext\", \"RedundantSuppression\")\n@RunWith(AndroidJUnit4::class)\nclass FlowsTest {\n    private val context = ApplicationProvider.getApplicationContext<Context>()\n    @get:Rule val temporaryFolder = TemporaryFolder()\n\n    @Before\n    fun setUp() {\n        GlideIdlingResources.initGlide()\n    }\n\n    @After\n    fun tearDown() {\n        Glide.tearDown()\n    }\n\n    @Test\n    fun flow_withPlaceholderDrawable_emitsPlaceholderDrawableFirst() = runTest {\n        val placeholderDrawable = ColorDrawable(Color.RED)\n        val first =\n            Glide.with(context)\n                .load(temporaryFolder.newFile())\n                .placeholder(placeholderDrawable)\n                .flow(100)\n                .first()\n\n        assertThat(first).isEqualTo(Placeholder<Drawable>(Status.RUNNING, placeholderDrawable))\n    }\n\n    @Test\n    fun flow_withNoPlaceholderDrawable_emitsNullPlaceholderFirst() = runTest {\n        val first = Glide.with(context).load(temporaryFolder.newFile()).flow(100).first()\n\n        assertThat(first).isEqualTo(Placeholder<Drawable>(Status.RUNNING, placeholder = null))\n    }\n\n    @Test\n    fun flow_failingNonNullModel_emitsRunningThenFailed() = runTest {\n        val missingResourceId = 123\n        val results = Glide.with(context).load(missingResourceId).flow(100).firstLoad().toList()\n\n        assertThat(results)\n            .containsExactly(\n                Placeholder<Drawable>(Status.RUNNING, placeholder = null),\n                Placeholder<Drawable>(Status.FAILED, placeholder = null),\n            )\n            .inOrder()\n    }\n\n    @Test\n    fun flow_failingNonNullModel_whenRestartedAfterFailure_emitsSecondLoad() = runTest {\n        val requestManager = Glide.with(context)\n        val missingResourceId = 123\n\n        val flow =\n            requestManager\n                .load(missingResourceId)\n                .listener(onFailure(atMostOnce { restartAllRequestsOnNewThread(requestManager) }))\n                .flow(100)\n\n        assertThat(flow.take(4).toList())\n            .comparingStatus()\n            .containsExactly(Status.RUNNING, Status.FAILED, Status.RUNNING, Status.FAILED)\n    }\n\n    @Test\n    fun flow_successfulNonNullModel_emitsRunningThenSuccess() = runTest {\n        val results = Glide.with(context).load(newImageFile()).flow(100).firstLoad().toList()\n\n        assertThat(results)\n            .compareStatusAndType()\n            .containsExactly(placeholder(Status.RUNNING), resource(Status.SUCCEEDED))\n            .inOrder()\n    }\n\n    @Test\n    fun flow_withNullModel_andFallbackDrawable_emitsFailureWithFallbackDrawable() = runTest {\n        val fallbackDrawable = ColorDrawable(Color.BLUE)\n        val first =\n            Glide.with(context).load(null as Uri?).fallback(fallbackDrawable).flow(100).first()\n        assertThat(first).isEqualTo(Placeholder<Drawable>(Status.FAILED, fallbackDrawable))\n    }\n\n    @Test\n    fun flow_successfulNonNullModel_whenRestartedAfterSuccess_emitsSecondLoad() = runTest {\n        val requestManager = Glide.with(context)\n\n        val flow =\n            requestManager\n                .load(newImageFile())\n                .listener(onSuccess(atMostOnce { restartAllRequestsOnNewThread(requestManager) }))\n                .flow(100)\n\n        assertThat(flow.take(4).toList())\n            .comparingStatus()\n            .containsExactly(\n                Status.RUNNING,\n                Status.SUCCEEDED,\n                Status.CLEARED, // See the TODO in RequestTracker#pauseAllRequests\n                Status\n                    .SUCCEEDED, // The request completes from in memory, so it never goes to RUNNING\n            )\n    }\n\n    @Test\n    fun flow_successfulNonNullModel_oneSuccessfulThumbnail_emitsThumbnailAndMainResources() =\n        runTest {\n            makeGlideSingleThreadedToOrderThumbnailRequests()\n\n            val output =\n                Glide.with(context)\n                    .load(newImageFile())\n                    .thumbnail(Glide.with(context).load(newImageFile()))\n                    .flow(100)\n                    .firstLoad()\n                    .toList()\n            assertThat(output)\n                .compareStatusAndType()\n                .containsExactly(\n                    placeholder(Status.RUNNING),\n                    resource(Status.RUNNING),\n                    resource(Status.SUCCEEDED),\n                )\n        }\n\n    @Test\n    fun flow_successfulNonNullModel_oneFailingThumbnail_emitMainResourceOnly() = runTest {\n        makeGlideSingleThreadedToOrderThumbnailRequests()\n\n        val missingResourceId = 123\n        val output =\n            Glide.with(context)\n                .load(newImageFile())\n                .thumbnail(Glide.with(context).load(missingResourceId))\n                .flow(100)\n                .firstLoad()\n                .toList()\n        assertThat(output)\n            .compareStatusAndType()\n            .containsExactly(placeholder(Status.RUNNING), resource(Status.SUCCEEDED))\n    }\n\n    @Test\n    fun flow_failingNonNullModel_successfulThumbnail_emitsThumbnailWithFailedStatus() = runTest {\n        makeGlideSingleThreadedToOrderThumbnailRequests()\n\n        val missingResourceId = 123\n        val output =\n            Glide.with(context)\n                .load(missingResourceId)\n                .thumbnail(Glide.with(context).load(newImageFile()))\n                .flow(100)\n                .firstLoad()\n                .toList()\n        assertThat(output)\n            .compareStatusAndType()\n            .containsExactly(\n                placeholder(Status.RUNNING),\n                resource(Status.RUNNING),\n                resource(Status.FAILED),\n            )\n    }\n\n    @Test\n    fun flow_failingNonNullModel_failingNonNullThumbnail_emitsRunningThenFailed() = runTest {\n        makeGlideSingleThreadedToOrderThumbnailRequests()\n\n        val missingResourceId = 123\n        val output =\n            Glide.with(context)\n                .load(missingResourceId)\n                .thumbnail(Glide.with(context).load(missingResourceId))\n                .flow(100)\n                .firstLoad()\n                .toList()\n\n        assertThat(output)\n            .compareStatusAndType()\n            .containsExactly(placeholder(Status.RUNNING), placeholder(Status.FAILED))\n    }\n\n    @Test\n    fun flow_failingNonNullModel_succeedingNonNullError_emitsRunningThenSuccess() = runTest {\n        makeGlideSingleThreadedToOrderThumbnailRequests()\n\n        val missingResourceId = 123\n        val output =\n            Glide.with(context)\n                .load(missingResourceId)\n                .error(Glide.with(context).load(newImageFile()))\n                .flow(100)\n                .firstLoad()\n                .toList()\n\n        assertThat(output)\n            .compareStatusAndType()\n            .containsExactly(\n                placeholder(Status.RUNNING),\n                // TODO(judds): This is probably another case where resource(Status.FAILURE) is more\n                //  appropriate. TO do so, we'd need to avoid passing TargetListener in\n                // RequestBuilder into\n                // thumbnails (and probably error request builders). That's a larger change\n                resource(Status.SUCCEEDED),\n            )\n    }\n\n    @Test\n    fun flow_failingNonNullModel_failingNonNullError_emitsRunningThenFailure() = runTest {\n        makeGlideSingleThreadedToOrderThumbnailRequests()\n\n        val missingResourceId = 123\n        val output =\n            Glide.with(context)\n                .load(missingResourceId)\n                .error(Glide.with(context).load(missingResourceId))\n                .flow(100)\n                .firstLoad()\n                .toList()\n\n        assertThat(output)\n            .compareStatusAndType()\n            .containsExactly(placeholder(Status.RUNNING), placeholder(Status.FAILED))\n    }\n\n    @Test\n    fun flow_failingNonNullModel_failingNonNullError_succeedingErrorThumbnail_emitsRunningThenRunningWithResourceThenFailureWithResource() =\n        runTest {\n            makeGlideSingleThreadedToOrderThumbnailRequests()\n\n            val missingResourceId = 123\n            val output =\n                Glide.with(context)\n                    .load(missingResourceId)\n                    .error(\n                        Glide.with(context)\n                            .load(missingResourceId)\n                            .thumbnail(Glide.with(context).load(newImageFile()))\n                    )\n                    .flow(100)\n                    .firstLoad()\n                    .toList()\n\n            assertThat(output)\n                .compareStatusAndType()\n                .containsExactly(\n                    placeholder(Status.RUNNING),\n                    resource(Status.RUNNING),\n                    resource(Status.FAILED),\n                )\n        }\n\n    @Test\n    fun flow_onClose_clearsTarget() = runTest {\n        val inCache = AtomicReference<com.bumptech.glide.load.engine.Resource<*>?>()\n        GlideIdlingResources.initGlide(\n            GlideBuilder()\n                .setMemoryCache(\n                    object : MemoryCache {\n                        override fun getCurrentSize(): Long = 0\n\n                        override fun getMaxSize(): Long = 0\n\n                        override fun setSizeMultiplier(multiplier: Float) {}\n\n                        override fun remove(key: Key): com.bumptech.glide.load.engine.Resource<*>? {\n                            return null\n                        }\n\n                        override fun setResourceRemovedListener(\n                            listener: MemoryCache.ResourceRemovedListener\n                        ) {}\n\n                        override fun clearMemory() {}\n\n                        override fun trimMemory(level: Int) {}\n\n                        override fun put(\n                            key: Key,\n                            resource: com.bumptech.glide.load.engine.Resource<*>?,\n                        ): com.bumptech.glide.load.engine.Resource<*>? {\n                            inCache.set(resource)\n                            return null\n                        }\n                    }\n                )\n        )\n        val data = Glide.with(context).load(newImageFile()).flow(100, 100).firstLoad().toList()\n        assertThat(data).isNotEmpty()\n        // Glide's executor (in EngineJob's notify loop) and the coroutine race to close the\n        // resource.\n        // If Glide's executor wins, then the coroutine will be able to put the resource in the\n        // cache,\n        // but if not, the item won't be cached until after the coroutine starts back up.\n        onIdle()\n        assertThat(inCache.get()).isNotNull()\n    }\n\n    @Test\n    fun flow_withOverrideSize_andProvidedSize_prefersOverrideSize() = runTest {\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context).load(FakeModel()).override(50, 60).flow(200, 100).firstLoad().toList()\n\n        assertThat(requestedSizeReference.get()).isEqualTo(Size(50, 60))\n    }\n\n    @Test\n    fun flow_withOnlyProvidedSize_usesProvidedSize() = runTest {\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context).load(FakeModel()).flow(100, 200).firstLoad().toList()\n\n        assertThat(requestedSizeReference.get()).isEqualTo(Size(100, 200))\n    }\n\n    @Test\n    fun flow_withOnlySingleDimension_usesProvidedSize() = runTest {\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context).load(FakeModel()).flow(150).firstLoad().toList()\n\n        assertThat(requestedSizeReference.get()).isEqualTo(Size(150, 150))\n    }\n\n    @Test\n    fun flow_withSizeOriginal_usesSizeOriginal() = runTest {\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context)\n            .load(FakeModel())\n            .flow(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n            .firstLoad()\n            .toList()\n\n        assertThat(requestedSizeReference.get())\n            .isEqualTo(Size(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL))\n    }\n\n    @Test\n    fun flow_withSizeOriginalOverride_concreteProvidedSize_usesSizeOriginal() = runTest {\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context)\n            .load(FakeModel())\n            .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n            .flow(200, 300)\n            .firstLoad()\n            .toList()\n\n        assertThat(requestedSizeReference.get())\n            .isEqualTo(Size(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL))\n    }\n\n    @Test\n    fun flow_withConcreteOverride_sizeOriginalProvidedSize_usesConcreteSize() = runTest {\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context)\n            .load(FakeModel())\n            .override(200, 300)\n            .flow(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)\n            .firstLoad()\n            .toList()\n\n        assertThat(requestedSizeReference.get()).isEqualTo(Size(200, 300))\n    }\n\n    @Test\n    fun flow_withThumbnailWithOverrideSize_usesOverrideSizeForThumbnail() = runTest {\n        makeGlideSingleThreadedToOrderThumbnailRequests()\n\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context)\n            .load(newImageFile())\n            .thumbnail(Glide.with(context).load(FakeModel()).override(100, 200))\n            .flow(Target.SIZE_ORIGINAL)\n            .firstLoad()\n            .toList()\n\n        assertThat(requestedSizeReference.get()).isEqualTo(Size(100, 200))\n    }\n\n    @Test\n    fun flow_withThumbnailWithoutOverrideSize_usesProvidedSizeForThumbnail() = runTest {\n        makeGlideSingleThreadedToOrderThumbnailRequests()\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context)\n            .load(newImageFile())\n            .thumbnail(Glide.with(context).load(FakeModel()))\n            .flow(300, 400)\n            .firstLoad()\n            .toList()\n\n        assertThat(requestedSizeReference.get()).isEqualTo(Size(300, 400))\n    }\n\n    @Test\n    fun flow_withInvalidProvidedWith_throws() = runTest {\n        val missingResourceId = 123\n        val requestBuilder = Glide.with(context).load(missingResourceId)\n\n        assertFailsWith<IllegalArgumentException> { requestBuilder.flow(-100, 100) }\n    }\n\n    @Test\n    fun flow_withInvalidProvidedHeight_throws() {\n        val missingResourceId = 123\n        val requestBuilder = Glide.with(context).load(missingResourceId)\n\n        assertFailsWith<IllegalArgumentException> { requestBuilder.flow(100, -100) }\n    }\n\n    @Test\n    fun flow_withAsyncSize_immediatelyEmitsPlaceholder() = runTest {\n        val placeholder = ColorDrawable(Color.GREEN)\n\n        val missingResourceId = 123\n        val result =\n            Glide.with(context)\n                .load(missingResourceId)\n                .placeholder(placeholder)\n                .flow(delayForever)\n                .first()\n\n        assertThat(result).isEqualTo(Placeholder<Drawable>(Status.RUNNING, placeholder))\n    }\n\n    @Test\n    fun flow_withAsyncSizeThatNeverCompletes_andOverrideSize_finishesSuccessfully() = runTest {\n        val result =\n            Glide.with(context)\n                .load(newImageFile())\n                .override(100, 100)\n                .flow(delayForever)\n                .firstLoad()\n                .toList()\n\n        assertThat(result)\n            .comparingStatus()\n            .containsExactly(Status.RUNNING, Status.SUCCEEDED)\n            .inOrder()\n    }\n\n    @Test\n    fun flow_withAsyncSize_andOverrideSize_usesOverrideSize() = runTest {\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context)\n            .load(FakeModel())\n            .override(200, 100)\n            .flow { Size(1, 2) }\n            .firstLoad()\n            .toList()\n\n        assertThat(requestedSizeReference.get()).isEqualTo(Size(200, 100))\n    }\n\n    @Test\n    fun flow_withAsyncSize_thumbnailWithConcreteSize_startsThumbnailWithoutWaitingForSize() =\n        runTest {\n            val result =\n                Glide.with(context)\n                    .load(newImageFile())\n                    .thumbnail(Glide.with(context).load(newImageFile()).override(25, 50))\n                    .flow(delayForever)\n                    .take(2)\n                    .toList()\n\n            assertThat(result)\n                .compareStatusAndType()\n                .containsExactly(placeholder(Status.RUNNING), resource(Status.RUNNING))\n                .inOrder()\n        }\n\n    @Test\n    fun flow_withAsyncSize_concreteSizeForThumbnail_startsMainRequestWhenAsyncSizeIsAvailable() =\n        runTest {\n            val waitForThumbnailToFinishChannel = Channel<Boolean>()\n            val waitForThumbnailToFinishSize: suspend () -> Size = {\n                waitForThumbnailToFinishChannel.receive()\n                Size(100, 200)\n            }\n\n            val result =\n                Glide.with(context)\n                    .load(newImageFile())\n                    .thumbnail(\n                        Glide.with(context)\n                            .load(newImageFile())\n                            .override(75, 50)\n                            .listener(\n                                onSuccess { launch { waitForThumbnailToFinishChannel.send(true) } }\n                            )\n                    )\n                    .flow(waitForThumbnailToFinishSize)\n                    .firstLoad()\n                    .toList()\n\n            assertThat(result)\n                .compareStatusAndType()\n                .containsExactly(\n                    placeholder(Status.RUNNING),\n                    resource(Status.RUNNING),\n                    resource(Status.SUCCEEDED),\n                )\n        }\n\n    // TODO(judds): Consider adding a test for invalid async sizes. It doesn't seem like Glide\n    // asserts on this in the existing framework, so it's probably not super important to do for\n    // flows, but it might be nice.\n\n    @Test\n    fun flow_withNoProvidedSize_overrideSizePresent_usesOverrideSize() = runTest {\n        val requestedSizeReference = registerSizeCapturingFakeModelLoader()\n\n        Glide.with(context).load(FakeModel()).override(4, 5).flow().firstLoad().toList()\n\n        assertThat(requestedSizeReference.get()).isEqualTo(Size(4, 5))\n    }\n\n    @Test\n    fun flow_withNoProvidedSize_overrideSizeMissing_throws() = runTest {\n        val requestBuilder = Glide.with(context).load(FakeModel())\n\n        assertFailsWith<IllegalArgumentException> { requestBuilder.flow() }\n    }\n\n    private val delayForever: suspend () -> Size = {\n        delay(kotlin.time.Duration.INFINITE)\n        throw RuntimeException()\n    }\n\n    private fun registerSizeCapturingFakeModelLoader(): AtomicReference<Size> {\n        val result = AtomicReference<Size>()\n        Glide.get(context)\n            .registry\n            .append(\n                FakeModel::class.java,\n                File::class.java,\n                SizeObservingFakeModelLoader.Factory(newImageFile(), result),\n            )\n        return result\n    }\n\n    // Avoid race conditions where the main request finishes first by making sure they execute\n    // sequentially using a single threaded executor.\n    private fun makeGlideSingleThreadedToOrderThumbnailRequests() {\n        Glide.init(\n            context,\n            GlideBuilder()\n                .setSourceExecutor(GlideExecutor.newSourceBuilder().setThreadCount(1).build()),\n        )\n    }\n\n    // Robolectric will produce a Bitmap from any File, but this is relatively easy and will work on\n    // emulators as well as robolectric.\n    private fun newImageFile(): File {\n        val file = temporaryFolder.newFile()\n        val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)\n        val canvas = Canvas(bitmap)\n        canvas.drawColor(Color.GREEN)\n        file.outputStream().use { bitmap.compress(Bitmap.CompressFormat.JPEG, 75, it) }\n        return file\n    }\n\n    class FakeModel\n\n    class SizeObservingFakeModelLoader(\n        private val fileLoader: ModelLoader<File, File>,\n        private val fakeResult: File,\n        private val sizeReference: AtomicReference<Size>,\n    ) : ModelLoader<FakeModel, File> {\n\n        override fun buildLoadData(\n            model: FakeModel,\n            width: Int,\n            height: Int,\n            options: Options,\n        ): ModelLoader.LoadData<File>? {\n            sizeReference.set(Size(width, height))\n            return fileLoader.buildLoadData(fakeResult, width, height, options)\n        }\n\n        override fun handles(model: FakeModel): Boolean = true\n\n        class Factory(\n            private val fakeResult: File,\n            private val sizeReference: AtomicReference<Size>,\n        ) : ModelLoaderFactory<FakeModel, File> {\n            override fun build(\n                multiFactory: MultiModelLoaderFactory\n            ): ModelLoader<FakeModel, File> {\n                return SizeObservingFakeModelLoader(\n                    multiFactory.build(File::class.java, File::class.java),\n                    fakeResult,\n                    sizeReference,\n                )\n            }\n\n            override fun teardown() {}\n        }\n    }\n}\n\nprivate fun atMostOnce(function: () -> Unit): () -> Unit {\n    var isCalled = false\n    return {\n        if (!isCalled) {\n            isCalled = true\n            function()\n        }\n    }\n}\n\nprivate fun <ResourceT> onSuccess(onSuccess: () -> Unit) =\n    simpleRequestListener<ResourceT>(onSuccess) {}\n\nprivate fun <ResourceT> onFailure(onFailure: () -> Unit) =\n    simpleRequestListener<ResourceT>({}, onFailure)\n\nprivate fun <ResourceT> simpleRequestListener(\n    onSuccess: () -> Unit,\n    onFailure: () -> Unit,\n): RequestListener<ResourceT> =\n    object : RequestListener<ResourceT> {\n        override fun onResourceReady(\n            resource: ResourceT?,\n            model: Any?,\n            target: Target<ResourceT>?,\n            dataSource: DataSource?,\n            isFirstResource: Boolean,\n        ): Boolean {\n            onSuccess()\n            return false\n        }\n\n        override fun onLoadFailed(\n            e: GlideException?,\n            model: Any?,\n            target: Target<ResourceT>?,\n            isFirstResource: Boolean,\n        ): Boolean {\n            onFailure()\n            return false\n        }\n    }\n\n// TODO(judds): This function may be useful in production code as well, consider exposing it.\nprivate fun <ResourceT> Flow<GlideFlowInstant<ResourceT>>.firstLoad():\n    Flow<GlideFlowInstant<ResourceT>> {\n    val originalFlow = this\n    return flow {\n        var completion: GlideFlowInstant<ResourceT>? = null\n        originalFlow\n            .takeWhile {\n                if (it.status != Status.SUCCEEDED && it.status != Status.FAILED) {\n                    true\n                } else {\n                    completion = it\n                    false\n                }\n            }\n            .collect { emit(it) }\n\n        emit(completion!!)\n    }\n}\n\n@OptIn(DelicateCoroutinesApi::class)\nprivate fun restartAllRequestsOnNewThread(requestManager: RequestManager) =\n    newSingleThreadContext(\"restart\").use {\n        it.executor.execute {\n            requestManager.pauseAllRequests()\n            requestManager.resumeRequests()\n        }\n    }\n\nprivate fun placeholder(status: Status) = StatusAndType(status, Placeholder::class)\n\nprivate fun resource(status: Status) = StatusAndType(status, Resource::class)\n\nprivate data class StatusAndType(val status: Status, val type: KClass<out GlideFlowInstant<*>>)\n\nprivate fun IterableSubject.compareStatusAndType() = comparingElementsUsing(statusAndType())\n\nprivate fun statusAndType(): Correspondence<GlideFlowInstant<*>, StatusAndType> =\n    ktCorrespondenceFrom(\"statusAndType\") { actual, expected ->\n        actual?.statusAndType() == expected\n    }\n\nprivate fun GlideFlowInstant<*>.statusAndType() =\n    StatusAndType(\n        status,\n        when (this) {\n            is Placeholder<*> -> Placeholder::class\n            is Resource<*> -> Resource::class\n        },\n    )\n\nprivate fun IterableSubject.comparingStatus() = comparingElementsUsing(status())\n\nprivate fun status(): Correspondence<GlideFlowInstant<*>, Status> =\n    ktCorrespondenceFrom(\"status\") { actual, expected -> actual?.status == expected }\n\nprivate fun <A, E> ktCorrespondenceFrom(\n    description: String,\n    predicate: Correspondence.BinaryPredicate<A, E>,\n) = Correspondence.from(predicate, description)\n"
  },
  {
    "path": "integration/ktx/src/test/java/com/bumptech/glide/load/engine/executor/GlideIdlingResourceInit.kt",
    "content": "package com.bumptech.glide.load.engine.executor\n\nimport androidx.test.core.app.ApplicationProvider\nimport androidx.test.espresso.IdlingRegistry\nimport androidx.test.espresso.idling.concurrent.IdlingThreadPoolExecutor\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.GlideBuilder\nimport java.util.concurrent.LinkedBlockingQueue\nimport java.util.concurrent.TimeUnit\n\nobject GlideIdlingResources {\n\n    fun initGlide(builder: GlideBuilder? = null) {\n        val registry = IdlingRegistry.getInstance()\n        val executor =\n            IdlingThreadPoolExecutor(\n                \"glide_test_thread\",\n                /* corePoolSize = */ 1,\n                /* maximumPoolSize = */ 1,\n                /* keepAliveTime = */ 1,\n                TimeUnit.SECONDS,\n                LinkedBlockingQueue(),\n            ) {\n                Thread(it)\n            }\n        val glideExecutor = GlideExecutor(executor)\n        Glide.init(\n            ApplicationProvider.getApplicationContext(),\n            (builder ?: GlideBuilder())\n                .setSourceExecutor(glideExecutor)\n                .setAnimationExecutor(glideExecutor)\n                .setDiskCacheExecutor(glideExecutor),\n        )\n        registry.register(executor)\n    }\n}\n"
  },
  {
    "path": "integration/okhttp/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.okhttp\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    annotationProcessor(project(\":annotation:compiler\"))\n\n    api(libs.okhttp2)\n    api(libs.androidx.annotation)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/okhttp/gradle.properties",
    "content": "POM_NAME=Glide OkHttp Integration\nPOM_ARTIFACT_ID=okhttp-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to use OkHttp 2.x to fetch data over http/https in Glide\n"
  },
  {
    "path": "integration/okhttp/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n    <!-- See https://github.com/square/okio/issues/58 -->\n    <issue id=\"InvalidPackage\" severity=\"ignore\">\n        <ignore regexp=\"okio-1.0.0.jar\"/>\n    </issue>\n</lint>\n"
  },
  {
    "path": "integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpGlideModule.java",
    "content": "package com.bumptech.glide.integration.okhttp;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport java.io.InputStream;\n\n/**\n * A {@link com.bumptech.glide.module.GlideModule} implementation to replace Glide's default {@link\n * java.net.HttpURLConnection} based {@link com.bumptech.glide.load.model.ModelLoader} with an\n * OkHttp based {@link com.bumptech.glide.load.model.ModelLoader}.\n *\n * <p>If you're using gradle, you can include this module simply by depending on the aar, the module\n * will be merged in by manifest merger. For other build systems or for more more information, see\n * {@link com.bumptech.glide.module.GlideModule}.\n *\n * @deprecated replaced with com.bumptech.glide.integration.okhttp3.OkHttpGlideModule.\n */\n@Deprecated\npublic class OkHttpGlideModule implements com.bumptech.glide.module.GlideModule {\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    // Do nothing.\n  }\n\n  @Override\n  public void registerComponents(Context context, Glide glide, Registry registry) {\n    registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());\n  }\n}\n"
  },
  {
    "path": "integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpLibraryGlideModule.java",
    "content": "package com.bumptech.glide.integration.okhttp;\n\nimport android.content.Context;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\nimport java.io.InputStream;\n\n/**\n * Registers OkHttp related classes via Glide's annotation processor.\n *\n * <p>For Applications that depend on this library and include an {@link AppGlideModule} and Glide's\n * annotation processor, this class will be automatically included.\n *\n * @deprecated Prefer the okhttp3 version instead.\n */\n@GlideModule\n@Deprecated\npublic class OkHttpLibraryGlideModule extends LibraryGlideModule {\n  @Override\n  public void registerComponents(Context context, Glide glide, Registry registry) {\n    registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());\n  }\n}\n"
  },
  {
    "path": "integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpStreamFetcher.java",
    "content": "package com.bumptech.glide.integration.okhttp;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.HttpException;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.util.ContentLengthInputStream;\nimport com.bumptech.glide.util.Synthetic;\nimport com.squareup.okhttp.OkHttpClient;\nimport com.squareup.okhttp.Request;\nimport com.squareup.okhttp.Response;\nimport com.squareup.okhttp.ResponseBody;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Map;\n\n/**\n * Fetches an {@link InputStream} using the okhttp library.\n *\n * @deprecated replaced with com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher.\n */\n@Deprecated\npublic class OkHttpStreamFetcher implements DataFetcher<InputStream> {\n  private static final String TAG = \"OkHttpFetcher\";\n  private final OkHttpClient client;\n  private final GlideUrl url;\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  InputStream stream;\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  ResponseBody responseBody;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public OkHttpStreamFetcher(OkHttpClient client, GlideUrl url) {\n    this.client = client;\n    this.url = url;\n  }\n\n  @Override\n  public void loadData(\n      @NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) {\n    Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());\n    for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {\n      String key = headerEntry.getKey();\n      requestBuilder.addHeader(key, headerEntry.getValue());\n    }\n    Request request = requestBuilder.build();\n\n    client\n        .newCall(request)\n        .enqueue(\n            new com.squareup.okhttp.Callback() {\n              @Override\n              public void onFailure(Request request, IOException e) {\n                if (Log.isLoggable(TAG, Log.DEBUG)) {\n                  Log.d(TAG, \"OkHttp failed to obtain result\", e);\n                }\n                callback.onLoadFailed(e);\n              }\n\n              @Override\n              public void onResponse(Response response) throws IOException {\n                responseBody = response.body();\n                if (response.isSuccessful()) {\n                  long contentLength = responseBody.contentLength();\n                  stream =\n                      ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);\n                  callback.onDataReady(stream);\n                } else {\n                  callback.onLoadFailed(new HttpException(response.message(), response.code()));\n                }\n              }\n            });\n  }\n\n  @Override\n  public void cleanup() {\n    try {\n      if (stream != null) {\n        stream.close();\n      }\n    } catch (IOException e) {\n      // Ignored\n    }\n    if (responseBody != null) {\n      try {\n        responseBody.close();\n      } catch (IOException e) {\n        // Ignored.\n      }\n    }\n  }\n\n  @Override\n  public void cancel() {\n    // TODO: call cancel on the client when this method is called on a background thread. See #257\n  }\n\n  @NonNull\n  @Override\n  public Class<InputStream> getDataClass() {\n    return InputStream.class;\n  }\n\n  @NonNull\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.REMOTE;\n  }\n}\n"
  },
  {
    "path": "integration/okhttp/src/main/java/com/bumptech/glide/integration/okhttp/OkHttpUrlLoader.java",
    "content": "package com.bumptech.glide.integration.okhttp;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.squareup.okhttp.OkHttpClient;\nimport java.io.InputStream;\n\n/**\n * A simple model loader for fetching media over http/https using OkHttp.\n *\n * @deprecated replaced with com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.\n */\n@Deprecated\npublic class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {\n\n  private final OkHttpClient client;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public OkHttpUrlLoader(OkHttpClient client) {\n    this.client = client;\n  }\n\n  @Override\n  public boolean handles(@NonNull GlideUrl url) {\n    return true;\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  @Override\n  public LoadData<InputStream> buildLoadData(\n      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {\n    return new LoadData<>(model, new OkHttpStreamFetcher(client, model));\n  }\n\n  /** The default factory for {@link OkHttpUrlLoader}s. */\n  // Public API.\n  @SuppressWarnings({\"WeakerAccess\", \"deprecation\"})\n  public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {\n    private static volatile OkHttpClient internalClient;\n    private final OkHttpClient client;\n\n    private static OkHttpClient getInternalClient() {\n      if (internalClient == null) {\n        synchronized (Factory.class) {\n          if (internalClient == null) {\n            internalClient = new OkHttpClient();\n          }\n        }\n      }\n      return internalClient;\n    }\n\n    /** Constructor for a new Factory that runs requests using a static singleton client. */\n    public Factory() {\n      this(getInternalClient());\n    }\n\n    /** Constructor for a new Factory that runs requests using given client. */\n    public Factory(OkHttpClient client) {\n      this.client = client;\n    }\n\n    @NonNull\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new OkHttpUrlLoader(client);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing, this instance doesn't own the client.\n    }\n  }\n}\n"
  },
  {
    "path": "integration/okhttp3/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.okhttp\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    annotationProcessor(project(\":annotation:compiler\"))\n\n    api(libs.okhttp3)\n    api(libs.androidx.annotation)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/okhttp3/gradle.properties",
    "content": "POM_NAME=Glide OkHttp 3.x Integration\nPOM_ARTIFACT_ID=okhttp3-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to use OkHttp 3.x to fetch data over http/https in Glide\n"
  },
  {
    "path": "integration/okhttp3/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n    <!-- See https://github.com/square/okio/issues/58 -->\n    <issue id=\"InvalidPackage\" severity=\"ignore\">\n        <ignore regexp=\"okio-1.0.0.jar\"/>\n    </issue>\n</lint>\n"
  },
  {
    "path": "integration/okhttp3/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application>\n        <meta-data\n            android:name=\"com.bumptech.glide.integration.okhttp3.OkHttpGlideModule\"\n            android:value=\"GlideModule\"/>\n    </application>\n</manifest>\n"
  },
  {
    "path": "integration/okhttp3/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpGlideModule.java",
    "content": "package com.bumptech.glide.integration.okhttp3;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport java.io.InputStream;\n\n/**\n * A {@link com.bumptech.glide.module.GlideModule} implementation to replace Glide's default {@link\n * java.net.HttpURLConnection} based {@link com.bumptech.glide.load.model.ModelLoader} with an\n * OkHttp based {@link com.bumptech.glide.load.model.ModelLoader}.\n *\n * <p>If you're using gradle, you can include this module simply by depending on the aar, the module\n * will be merged in by manifest merger. For other build systems or for more more information, see\n * {@link com.bumptech.glide.module.GlideModule}.\n *\n * @deprecated Replaced by {@link OkHttpLibraryGlideModule} for Applications that use Glide's\n *     annotations.\n */\n@Deprecated\npublic class OkHttpGlideModule implements com.bumptech.glide.module.GlideModule {\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    // Do nothing.\n  }\n\n  @Override\n  public void registerComponents(Context context, Glide glide, Registry registry) {\n    registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());\n  }\n}\n"
  },
  {
    "path": "integration/okhttp3/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpLibraryGlideModule.java",
    "content": "package com.bumptech.glide.integration.okhttp3;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\nimport java.io.InputStream;\n\n/**\n * Registers OkHttp related classes via Glide's annotation processor.\n *\n * <p>For Applications that depend on this library and include an {@link AppGlideModule} and Glide's\n * annotation processor, this class will be automatically included.\n */\n@GlideModule\npublic final class OkHttpLibraryGlideModule extends LibraryGlideModule {\n  @Override\n  public void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());\n  }\n}\n"
  },
  {
    "path": "integration/okhttp3/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpStreamFetcher.java",
    "content": "package com.bumptech.glide.integration.okhttp3;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.HttpException;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.util.ContentLengthInputStream;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Map;\nimport okhttp3.Call;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\n\n/** Fetches an {@link InputStream} using the okhttp library. */\npublic class OkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback {\n  private static final String TAG = \"OkHttpFetcher\";\n  private final Call.Factory client;\n  private final GlideUrl url;\n  private InputStream stream;\n  private ResponseBody responseBody;\n  private DataCallback<? super InputStream> callback;\n  // call may be accessed on the main thread while the object is in use on other threads. All other\n  // accesses to variables may occur on different threads, but only one at a time.\n  private volatile Call call;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public OkHttpStreamFetcher(Call.Factory client, GlideUrl url) {\n    this.client = client;\n    this.url = url;\n  }\n\n  @Override\n  public void loadData(\n      @NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) {\n    Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());\n    for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {\n      String key = headerEntry.getKey();\n      requestBuilder.addHeader(key, headerEntry.getValue());\n    }\n    Request request = requestBuilder.build();\n    this.callback = callback;\n\n    call = client.newCall(request);\n    call.enqueue(this);\n  }\n\n  @Override\n  public void onFailure(@NonNull Call call, @NonNull IOException e) {\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      Log.d(TAG, \"OkHttp failed to obtain result\", e);\n    }\n\n    callback.onLoadFailed(e);\n  }\n\n  @Override\n  public void onResponse(@NonNull Call call, @NonNull Response response) {\n    responseBody = response.body();\n    if (response.isSuccessful()) {\n      long contentLength = Preconditions.checkNotNull(responseBody).contentLength();\n      stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);\n      callback.onDataReady(stream);\n    } else {\n      callback.onLoadFailed(new HttpException(response.message(), response.code()));\n    }\n  }\n\n  @Override\n  public void cleanup() {\n    try {\n      if (stream != null) {\n        stream.close();\n      }\n    } catch (IOException e) {\n      // Ignored\n    }\n    if (responseBody != null) {\n      responseBody.close();\n    }\n    callback = null;\n  }\n\n  @Override\n  public void cancel() {\n    Call local = call;\n    if (local != null) {\n      local.cancel();\n    }\n  }\n\n  @NonNull\n  @Override\n  public Class<InputStream> getDataClass() {\n    return InputStream.class;\n  }\n\n  @NonNull\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.REMOTE;\n  }\n}\n"
  },
  {
    "path": "integration/okhttp3/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpUrlLoader.java",
    "content": "package com.bumptech.glide.integration.okhttp3;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport java.io.InputStream;\nimport okhttp3.Call;\nimport okhttp3.OkHttpClient;\n\n/** A simple model loader for fetching media over http/https using OkHttp. */\npublic class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {\n\n  private final Call.Factory client;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public OkHttpUrlLoader(@NonNull Call.Factory client) {\n    this.client = client;\n  }\n\n  @Override\n  public boolean handles(@NonNull GlideUrl url) {\n    return true;\n  }\n\n  @Override\n  public LoadData<InputStream> buildLoadData(\n      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {\n    return new LoadData<>(model, new OkHttpStreamFetcher(client, model));\n  }\n\n  /** The default factory for {@link OkHttpUrlLoader}s. */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {\n    private static volatile Call.Factory internalClient;\n    private final Call.Factory client;\n\n    private static Call.Factory getInternalClient() {\n      if (internalClient == null) {\n        synchronized (Factory.class) {\n          if (internalClient == null) {\n            internalClient = new OkHttpClient();\n          }\n        }\n      }\n      return internalClient;\n    }\n\n    /** Constructor for a new Factory that runs requests using a static singleton client. */\n    public Factory() {\n      this(getInternalClient());\n    }\n\n    /**\n     * Constructor for a new Factory that runs requests using given client.\n     *\n     * @param client this is typically an instance of {@code OkHttpClient}.\n     */\n    public Factory(@NonNull Call.Factory client) {\n      this.client = client;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new OkHttpUrlLoader(client);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing, this instance doesn't own the client.\n    }\n  }\n}\n"
  },
  {
    "path": "integration/okhttp4/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.okhttp\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.okhttp.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    annotationProcessor(project(\":annotation:compiler\"))\n\n    api(libs.okhttp4)\n    api(libs.androidx.annotation)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/okhttp4/gradle.properties",
    "content": "POM_NAME=Glide OkHttp 4.x Integration\nPOM_ARTIFACT_ID=okhttp4-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to use OkHttp 4.x to fetch data over http/https in Glide\n"
  },
  {
    "path": "integration/okhttp4/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n    </issue>\n</lint>\n"
  },
  {
    "path": "integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpLibraryGlideModule.java",
    "content": "package com.bumptech.glide.integration.okhttp3;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\nimport java.io.InputStream;\n\n/**\n * Registers OkHttp related classes via Glide's annotation processor.\n *\n * <p>For Applications that depend on this library and include an {@link AppGlideModule} and Glide's\n * annotation processor, this class will be automatically included.\n */\n@GlideModule\npublic final class OkHttpLibraryGlideModule extends LibraryGlideModule {\n  @Override\n  public void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());\n  }\n}\n"
  },
  {
    "path": "integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpStreamFetcher.java",
    "content": "package com.bumptech.glide.integration.okhttp3;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.HttpException;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.util.ContentLengthInputStream;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Map;\nimport okhttp3.Call;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\n\n/** Fetches an {@link InputStream} using the okhttp library. */\npublic class OkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback {\n  private static final String TAG = \"OkHttpFetcher\";\n  private final Call.Factory client;\n  private final GlideUrl url;\n  private InputStream stream;\n  private ResponseBody responseBody;\n  private DataCallback<? super InputStream> callback;\n  // call may be accessed on the main thread while the object is in use on other threads. All other\n  // accesses to variables may occur on different threads, but only one at a time.\n  private volatile Call call;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public OkHttpStreamFetcher(Call.Factory client, GlideUrl url) {\n    this.client = client;\n    this.url = url;\n  }\n\n  @Override\n  public void loadData(\n      @NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) {\n    Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());\n    for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {\n      String key = headerEntry.getKey();\n      requestBuilder.addHeader(key, headerEntry.getValue());\n    }\n    Request request = requestBuilder.build();\n    this.callback = callback;\n\n    call = client.newCall(request);\n    call.enqueue(this);\n  }\n\n  @Override\n  public void onFailure(@NonNull Call call, @NonNull IOException e) {\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      Log.d(TAG, \"OkHttp failed to obtain result\", e);\n    }\n\n    callback.onLoadFailed(e);\n  }\n\n  @Override\n  public void onResponse(@NonNull Call call, @NonNull Response response) {\n    responseBody = response.body();\n    if (response.isSuccessful()) {\n      long contentLength = Preconditions.checkNotNull(responseBody).contentLength();\n      stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);\n      callback.onDataReady(stream);\n    } else {\n      callback.onLoadFailed(new HttpException(response.message(), response.code()));\n    }\n  }\n\n  @Override\n  public void cleanup() {\n    try {\n      if (stream != null) {\n        stream.close();\n      }\n    } catch (IOException e) {\n      // Ignored\n    }\n    if (responseBody != null) {\n      responseBody.close();\n    }\n    callback = null;\n  }\n\n  @Override\n  public void cancel() {\n    Call local = call;\n    if (local != null) {\n      local.cancel();\n    }\n  }\n\n  @NonNull\n  @Override\n  public Class<InputStream> getDataClass() {\n    return InputStream.class;\n  }\n\n  @NonNull\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.REMOTE;\n  }\n}\n"
  },
  {
    "path": "integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpUrlLoader.java",
    "content": "package com.bumptech.glide.integration.okhttp3;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport java.io.InputStream;\nimport okhttp3.Call;\nimport okhttp3.OkHttpClient;\n\n/** A simple model loader for fetching media over http/https using OkHttp. */\npublic class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {\n\n  private final Call.Factory client;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public OkHttpUrlLoader(@NonNull Call.Factory client) {\n    this.client = client;\n  }\n\n  @Override\n  public boolean handles(@NonNull GlideUrl url) {\n    return true;\n  }\n\n  @Override\n  public LoadData<InputStream> buildLoadData(\n      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {\n    return new LoadData<>(model, new OkHttpStreamFetcher(client, model));\n  }\n\n  /** The default factory for {@link OkHttpUrlLoader}s. */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {\n    private static volatile Call.Factory internalClient;\n    private final Call.Factory client;\n\n    private static Call.Factory getInternalClient() {\n      if (internalClient == null) {\n        synchronized (Factory.class) {\n          if (internalClient == null) {\n            internalClient = new OkHttpClient();\n          }\n        }\n      }\n      return internalClient;\n    }\n\n    /** Constructor for a new Factory that runs requests using a static singleton client. */\n    public Factory() {\n      this(getInternalClient());\n    }\n\n    /**\n     * Constructor for a new Factory that runs requests using given client.\n     *\n     * @param client this is typically an instance of {@code OkHttpClient}.\n     */\n    public Factory(@NonNull Call.Factory client) {\n      this.client = client;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new OkHttpUrlLoader(client);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing, this instance doesn't own the client.\n    }\n  }\n}\n"
  },
  {
    "path": "integration/recyclerview/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.recyclerview\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\n\ndependencies {\n    implementation(project(\":library\"))\n    compileOnly(libs.androidx.recyclerview)\n    compileOnly(libs.androidx.fragment)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/recyclerview/gradle.properties",
    "content": "POM_NAME=Glide RecyclerView Integration\nPOM_ARTIFACT_ID=recyclerview-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to display images in RecyclerView.\n"
  },
  {
    "path": "integration/recyclerview/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n    <issue id=\"GradleDependency\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "integration/recyclerview/src/main/java/com/bumptech/glide/integration/recyclerview/RecyclerToListViewScrollListener.java",
    "content": "package com.bumptech.glide.integration.recyclerview;\n\nimport android.widget.AbsListView;\nimport android.widget.ListView;\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Converts {@link androidx.recyclerview.widget.RecyclerView.OnScrollListener} events to {@link\n * AbsListView} scroll events.\n *\n * <p>Requires that the recycler view be using a {@link LinearLayoutManager} subclass.\n */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic final class RecyclerToListViewScrollListener extends RecyclerView.OnScrollListener {\n  public static final int UNKNOWN_SCROLL_STATE = Integer.MIN_VALUE;\n  private final AbsListView.OnScrollListener scrollListener;\n  private int lastFirstVisible = -1;\n  private int lastVisibleCount = -1;\n  private int lastItemCount = -1;\n\n  public RecyclerToListViewScrollListener(@NonNull AbsListView.OnScrollListener scrollListener) {\n    this.scrollListener = scrollListener;\n  }\n\n  @Override\n  public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n    int listViewState;\n    switch (newState) {\n      case RecyclerView.SCROLL_STATE_DRAGGING:\n        listViewState = ListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;\n        break;\n      case RecyclerView.SCROLL_STATE_IDLE:\n        listViewState = ListView.OnScrollListener.SCROLL_STATE_IDLE;\n        break;\n      case RecyclerView.SCROLL_STATE_SETTLING:\n        listViewState = ListView.OnScrollListener.SCROLL_STATE_FLING;\n        break;\n      default:\n        listViewState = UNKNOWN_SCROLL_STATE;\n    }\n\n    scrollListener.onScrollStateChanged(null /*view*/, listViewState);\n  }\n\n  @Override\n  public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n    LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();\n\n    int firstVisible = layoutManager.findFirstVisibleItemPosition();\n    int visibleCount = Math.abs(firstVisible - layoutManager.findLastVisibleItemPosition());\n    int itemCount = recyclerView.getAdapter().getItemCount();\n\n    if (firstVisible != lastFirstVisible\n        || visibleCount != lastVisibleCount\n        || itemCount != lastItemCount) {\n      scrollListener.onScroll(null, firstVisible, visibleCount, itemCount);\n      lastFirstVisible = firstVisible;\n      lastVisibleCount = visibleCount;\n      lastItemCount = itemCount;\n    }\n  }\n}\n"
  },
  {
    "path": "integration/recyclerview/src/main/java/com/bumptech/glide/integration/recyclerview/RecyclerViewPreloader.java",
    "content": "package com.bumptech.glide.integration.recyclerview;\n\nimport android.app.Activity;\nimport androidx.annotation.NonNull;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.recyclerview.widget.RecyclerView;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.ListPreloader;\nimport com.bumptech.glide.ListPreloader.PreloadModelProvider;\nimport com.bumptech.glide.ListPreloader.PreloadSizeProvider;\nimport com.bumptech.glide.RequestManager;\n\n/**\n * Loads a few resources ahead in the direction of scrolling in any {@link RecyclerView} so that\n * images are in the memory cache just before the corresponding view in created in the list. Gives\n * the appearance of an infinitely large image cache, depending on scrolling speed, cpu speed, and\n * cache size.\n *\n * <p>Must be added as a listener to the {@link RecyclerView} using {@link\n * RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener)}, or have its corresponding\n * methods called from another {@link androidx.recyclerview.widget.RecyclerView.OnScrollListener} to\n * function.\n *\n * <p>This class only works with {@link androidx.recyclerview.widget.LinearLayoutManager} and\n * subclasses of {@link androidx.recyclerview.widget.LinearLayoutManager}.\n *\n * @param <T> The type of the model being displayed in the {@link RecyclerView}.\n */\n@SuppressWarnings(\"unused\")\npublic final class RecyclerViewPreloader<T> extends RecyclerView.OnScrollListener {\n\n  private final RecyclerToListViewScrollListener recyclerScrollListener;\n\n  /** Helper constructor that accepts an {@link Activity}. */\n  public RecyclerViewPreloader(\n      @NonNull Activity activity,\n      @NonNull PreloadModelProvider<T> preloadModelProvider,\n      @NonNull PreloadSizeProvider<T> preloadDimensionProvider,\n      int maxPreload) {\n    this(Glide.with(activity), preloadModelProvider, preloadDimensionProvider, maxPreload);\n  }\n\n  /** Helper constructor that accepts an {@link FragmentActivity}. */\n  public RecyclerViewPreloader(\n      @NonNull FragmentActivity fragmentActivity,\n      @NonNull PreloadModelProvider<T> preloadModelProvider,\n      @NonNull PreloadSizeProvider<T> preloadDimensionProvider,\n      int maxPreload) {\n    this(Glide.with(fragmentActivity), preloadModelProvider, preloadDimensionProvider, maxPreload);\n  }\n\n  /** Helper constructor that accepts an {@link Fragment}. */\n  public RecyclerViewPreloader(\n      @NonNull Fragment fragment,\n      @NonNull PreloadModelProvider<T> preloadModelProvider,\n      @NonNull PreloadSizeProvider<T> preloadDimensionProvider,\n      int maxPreload) {\n    this(Glide.with(fragment), preloadModelProvider, preloadDimensionProvider, maxPreload);\n  }\n\n  /**\n   * Helper constructor that accepts an {@link android.app.Fragment}.\n   *\n   * @deprecated Use constructor <code>RecyclerViewPreloader(Fragment, PreloadModelProvider<T>,\n   * PreloadSizeProvider<T>)</code> instead.\n   */\n  @Deprecated\n  public RecyclerViewPreloader(\n      @NonNull android.app.Fragment fragment,\n      @NonNull PreloadModelProvider<T> preloadModelProvider,\n      @NonNull PreloadSizeProvider<T> preloadDimensionProvider,\n      int maxPreload) {\n    this(Glide.with(fragment), preloadModelProvider, preloadDimensionProvider, maxPreload);\n  }\n\n  /**\n   * Constructor that accepts interfaces for providing the dimensions of images to preload, the list\n   * of models to preload for a given position, and the request to use to load images.\n   *\n   * @param preloadModelProvider Provides models to load and requests capable of loading them.\n   * @param preloadDimensionProvider Provides the dimensions of images to load.\n   * @param maxPreload Maximum number of items to preload.\n   */\n  public RecyclerViewPreloader(\n      @NonNull RequestManager requestManager,\n      @NonNull PreloadModelProvider<T> preloadModelProvider,\n      @NonNull PreloadSizeProvider<T> preloadDimensionProvider,\n      int maxPreload) {\n\n    ListPreloader<T> listPreloader =\n        new ListPreloader<>(\n            requestManager, preloadModelProvider, preloadDimensionProvider, maxPreload);\n    recyclerScrollListener = new RecyclerToListViewScrollListener(listPreloader);\n  }\n\n  @Override\n  public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n    recyclerScrollListener.onScrolled(recyclerView, dx, dy);\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n    id(\"kotlin-android\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.sqljournaldiskcache\"\n\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig { minSdk = libs.versions.min.sdk.version.get().toInt() }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    implementation(libs.errorprone.annotations)\n\n    testImplementation(libs.guava.testlib)\n    testImplementation(libs.truth)\n    testImplementation(libs.junit)\n    testImplementation(libs.mockito.core)\n    testImplementation(libs.robolectric)\n    testImplementation(libs.androidx.test.core)\n    testImplementation(libs.androidx.junit)\n    testImplementation(libs.androidx.test.runner)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")\n"
  },
  {
    "path": "integration/sqljournaldiskcache/gradle.properties",
    "content": "POM_NAME=Glide SQL Journaled Disk Cache\nPOM_ARTIFACT_ID=sqljournaldiskcache\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=A sql journaled LRU disk cache alternative to Glide's standard disk cache\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/Clock.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\n/**\n * A simple wrapper for obtaining the current time for testing.\n *\n * <p>While this interface exists in lots of libraries, especially internally at Google, there\n * doesn't seem to be a reasonable public version. For now we're just duplicating it again in Glide\n * so that the library can be open sourced.\n */\npublic interface Clock {\n  long currentTimeMillis();\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/DefaultClock.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nfinal class DefaultClock implements Clock {\n  @Override\n  public long currentTimeMillis() {\n    return System.currentTimeMillis();\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/DiskCacheDbHelper.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport androidx.annotation.VisibleForTesting;\n\n/** The database helper for managing tables for {@link JournaledLruDiskCache}. */\nfinal class DiskCacheDbHelper extends SQLiteOpenHelper {\n  private static final int DATABASE_VERSION = 2; // judds.\n  private static final String DATABASE_NAME = \"disk_cache\";\n\n  static DiskCacheDbHelper forProd(Context context) {\n    return new DiskCacheDbHelper(context, /* isInMemory= */ false);\n  }\n\n  static DiskCacheDbHelper forTesting(Context context) {\n    return new DiskCacheDbHelper(context, /* isInMemory= */ true);\n  }\n\n  private DiskCacheDbHelper(Context context, boolean isInMemory) {\n    this(context, isInMemory, DATABASE_VERSION);\n  }\n\n  @VisibleForTesting\n  DiskCacheDbHelper(Context context, boolean isInMemory, int databaseVersion) {\n    super(context, isInMemory ? null : DATABASE_NAME, /* factory= */ null, databaseVersion);\n    setWriteAheadLoggingEnabled(true);\n  }\n\n  @Override\n  public void onCreate(SQLiteDatabase db) {\n    db.execSQL(JournalTable.getSqlCreateStatement());\n    db.execSQL(JournalTable.getIndexString());\n    db.execSQL(SizeTable.getSqlCreateStatement());\n  }\n\n  @Override\n  public void onOpen(SQLiteDatabase db) {\n    db.execSQL(\"PRAGMA legacy_alter_table=ON\");\n    db.setForeignKeyConstraintsEnabled(false);\n    try {\n      super.onOpen(db);\n    } finally {\n      db.setForeignKeyConstraintsEnabled(true);\n    }\n  }\n\n  // We're matching the existing production behavior, which uses STRING even though it should use\n  // TEXT\n  @SuppressLint(\"SQLiteString\")\n  @Override\n  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n    if (oldVersion < 2) {\n      // Dropping the journal table will also drop the index: https://sqlite.org/lang_droptable.html\n      db.execSQL(\"DROP TABLE IF EXISTS journal\");\n      db.execSQL(\"DROP TABLE IF EXISTS size\");\n\n      db.execSQL(\n          \"CREATE TABLE journal(\"\n              + \"key STRING PRIMARY KEY, \"\n              + \"last_modified_time INTEGER NOT NULL, \"\n              + \"pending_delete INTEGER NOT NULL DEFAULT 0, \"\n              + \"size INTEGER NOT NULL\"\n              + \")\");\n      db.execSQL(\n          \"CREATE INDEX journal_timestamp_key_idx\"\n              + \" ON journal (\"\n              + \"last_modified_time, \"\n              + \"key\"\n              + \")\");\n      db.execSQL(\n          \"CREATE TABLE size(\"\n              + \"id INTEGER PRIMARY KEY, \"\n              + \"size INTEGER NOT NULL DEFAULT 0\"\n              + \")\");\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/EntryCache.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport androidx.collection.ArrayMap;\nimport com.bumptech.glide.util.LruCache;\nimport java.io.File;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\n\n/**\n * Maintains an LRU cache of {@link String} keys to {@link Entry Entrys} where each entry contains a\n * read/write lock used to guarantee state and entries that are currently locked are guaranteed not\n * to be evicted.\n */\nfinal class EntryCache {\n  private final ArrayMap<String, Entry> activeEntries = new ArrayMap<>();\n  private final LruCache<String, Entry> inactiveEntries = new LruCache<>(6000);\n\n  synchronized void clear() {\n    activeEntries.clear();\n    inactiveEntries.clearMemory();\n  }\n\n  synchronized Entry get(String key) {\n    Entry entry = activeEntries.get(key);\n    if (entry == null) {\n      entry = inactiveEntries.get(key);\n      if (entry == null) {\n        entry = new Entry(key, this);\n        activeEntries.put(key, entry);\n      }\n    }\n    return entry;\n  }\n\n  private synchronized void removeFromActive(Entry entry) {\n    activeEntries.remove(entry.key);\n    inactiveEntries.put(entry.key, entry);\n  }\n\n  private synchronized void addToActive(Entry entry) {\n    inactiveEntries.remove(entry.key);\n    activeEntries.put(entry.key, entry);\n  }\n\n  static final class Entry {\n    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();\n    private final String key;\n    private final EntryCache cache;\n\n    private int lockCount;\n    private State state = State.UNKNOWN;\n    private File file;\n\n    Entry(String key, EntryCache cache) {\n      this.key = key;\n      this.cache = cache;\n    }\n\n    File getFile() {\n      return file;\n    }\n\n    boolean isStateKnown() {\n      return state != State.UNKNOWN;\n    }\n\n    boolean isPresent() {\n      return state == State.PRESENT;\n    }\n\n    void setPresent(File file) {\n      this.file = file;\n      state = State.PRESENT;\n    }\n\n    void setUnknown() {\n      state = State.UNKNOWN;\n    }\n\n    void setNotPresent() {\n      state = State.NOT_PRESENT;\n    }\n\n    void acquireReadLock() {\n      maybeSetActive();\n      readWriteLock.readLock().lock();\n    }\n\n    void releaseReadLock() {\n      ReentrantReadWriteLock lock = readWriteLock;\n      maybeSetInactive();\n      lock.readLock().unlock();\n    }\n\n    void acquireWriteLock() {\n      maybeSetActive();\n      readWriteLock.writeLock().lock();\n    }\n\n    void releaseWriteLock() {\n      ReentrantReadWriteLock lock = readWriteLock;\n      maybeSetInactive();\n      lock.writeLock().unlock();\n    }\n\n    private synchronized void maybeSetActive() {\n      lockCount++;\n      if (lockCount == 1) {\n        cache.addToActive(this);\n        readWriteLock = new ReentrantReadWriteLock();\n      }\n    }\n\n    private synchronized void maybeSetInactive() {\n      lockCount--;\n      if (lockCount == 0) {\n        cache.removeFromActive(this);\n        readWriteLock = null;\n      }\n    }\n\n    private enum State {\n      UNKNOWN,\n      PRESENT,\n      NOT_PRESENT,\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/EvictionManager.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.util.Log;\nimport androidx.annotation.GuardedBy;\nimport java.io.File;\nimport java.util.List;\n\nfinal class EvictionManager {\n  private static final String TAG = \"Evictor\";\n  // You must restart the app after enabling these logs for the change to take affect.\n  // We cache isLoggable to avoid the performance hit of checking repeatedly.\n  private static final boolean LOG_DEBUG = Log.isLoggable(TAG, Log.DEBUG);\n  private static final boolean LOG_VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);\n\n  // The maximum amount we can go over our cache size before triggering evictions, currently 25mb.\n  private static final long MAXIMUM_EVICTION_SLOP = 25 * 1024 * 1024;\n\n  private final Handler evictionHandler;\n  private final JournaledLruDiskCache diskCache;\n  private final File cacheDirectory;\n  private final FileSystem fileSystem;\n  private final Journal journal;\n  private final Looper workLooper;\n  private final Clock clock;\n  private final long evictionSlopBytes;\n  private final long staleEvictionThresholdMs;\n\n  @GuardedBy(\"this\")\n  private long maximumSizeBytes;\n\n  EvictionManager(\n      JournaledLruDiskCache diskCache,\n      File cacheDirectory,\n      FileSystem fileSystem,\n      Journal journal,\n      Looper workLooper,\n      long maximumSizeBytes,\n      float slopMultiplier,\n      long staleEvictionThresholdMs,\n      Clock clock) {\n    this.diskCache = diskCache;\n    this.cacheDirectory = cacheDirectory;\n    this.fileSystem = fileSystem;\n    this.journal = journal;\n    this.workLooper = workLooper;\n    this.maximumSizeBytes = maximumSizeBytes;\n    this.clock = clock;\n    this.staleEvictionThresholdMs = staleEvictionThresholdMs;\n\n    evictionSlopBytes =\n        Math.min(Math.round(maximumSizeBytes * slopMultiplier), MAXIMUM_EVICTION_SLOP);\n    evictionHandler = new Handler(workLooper, new EvictionCallback());\n  }\n\n  /**\n   * Sets maximumSizeBytes to a new size.\n   *\n   * <p>Must be called on a background thread.\n   *\n   * <p>Decreasing the maximumSizeBytes may schedule an eviction if the current cache size exceeds\n   * the new maximumSizeBytes. Evictions will be scheduled and executed asynchronously. Therefore,\n   * the eviction will happen based on the latest maximum cache size, not the maximum size at\n   * scheduling.\n   */\n  synchronized void setMaximumSizeBytes(long newMaxSizeBytes) {\n    long originalMaxBytes = maximumSizeBytes;\n    maximumSizeBytes = newMaxSizeBytes;\n    if (newMaxSizeBytes < originalMaxBytes) {\n      maybeScheduleEviction(newMaxSizeBytes);\n    }\n  }\n\n  private synchronized long getMaximumSizeBytes() {\n    return maximumSizeBytes;\n  }\n\n  /**\n   * Schedules a journal eviction on a work thread if the journal size currently exceeds the allowed\n   * cache size.\n   */\n  void maybeScheduleEviction() {\n    maybeScheduleEviction(getMaximumSizeBytes());\n  }\n\n  private void maybeScheduleEviction(long maximumSizeBytes) {\n    if (isEvictionRequired(maximumSizeBytes)) {\n      evictionHandler.obtainMessage(MessageIds.EVICT).sendToTarget();\n    }\n  }\n\n  private boolean isEvictionRequired(long maximumSizeBytes) {\n    return journal.getCurrentSizeBytes() > evictionSlopBytes + maximumSizeBytes;\n  }\n\n  private void evictOnWorkThread() {\n    if (!Looper.myLooper().equals(workLooper)) {\n      throw new IllegalStateException(\n          \"Cannot call evictOnWorkThread on thread: \" + Thread.currentThread().getName());\n    }\n    long maximumSizeBytes = getMaximumSizeBytes();\n    long staleDateMs = clock.currentTimeMillis() - staleEvictionThresholdMs;\n    List<String> staleEntriesKeys = journal.getStaleEntries(staleDateMs);\n    // Writes may queue up a number of eviction messages. After the first one runs, eviction may no\n    // longer be necessary, so we simply ignore the message.\n    if (!isEvictionRequired(maximumSizeBytes) && staleEntriesKeys.isEmpty()) {\n      if (LOG_VERBOSE) {\n        Log.v(TAG, \"Ignoring eviction, not needed\");\n      }\n      return;\n    }\n    if (LOG_DEBUG) {\n      Log.d(TAG, \"Starting eviction on work thread\");\n    }\n\n    int successfullyDeletedCount = 0;\n    int triedToDeleteEntries = staleEntriesKeys.size();\n    if (!staleEntriesKeys.isEmpty()) {\n      successfullyDeletedCount += diskCache.delete(staleEntriesKeys).size();\n    }\n\n    long targetSize = maximumSizeBytes - evictionSlopBytes;\n    if (isEvictionRequired(maximumSizeBytes)) {\n      long bytesToEvict = journal.getCurrentSizeBytes() - targetSize;\n      List<String> leastRecentlyUsedKeys = journal.getLeastRecentlyUsed(bytesToEvict);\n      triedToDeleteEntries += leastRecentlyUsedKeys.size();\n      successfullyDeletedCount += diskCache.delete(leastRecentlyUsedKeys).size();\n    }\n\n    if (triedToDeleteEntries == 0) {\n      throw new IllegalStateException(\"Failed to find entries to evict.\");\n    }\n\n    if (LOG_DEBUG) {\n      Log.d(\n          TAG,\n          \"Ran eviction\"\n              + \", tried to delete: \"\n              + triedToDeleteEntries\n              + \" entries\"\n              + \", actually deleted: \"\n              + successfullyDeletedCount\n              + \" entries\"\n              + \", target journal : \"\n              + targetSize\n              + \", journal size: \"\n              + journal.getCurrentSizeBytes()\n              + \", file size: \"\n              + fileSystem.getDirectorySize(cacheDirectory));\n    }\n  }\n\n  private class EvictionCallback implements Handler.Callback {\n    @Override\n    public boolean handleMessage(Message msg) {\n      if (msg.what != MessageIds.EVICT) {\n        return false;\n      }\n      evictOnWorkThread();\n      return true;\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/FileSystem.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * Wraps a few common {@link File} methods to provide a few higher level functions and allow for\n * mocking uncommon error cases.\n */\ninterface FileSystem {\n\n  default boolean delete(File file) {\n    return file.delete();\n  }\n\n  default boolean exists(File file) {\n    return file.exists();\n  }\n\n  default boolean createNewFile(File file) throws IOException {\n    return file.createNewFile();\n  }\n\n  default boolean rename(File from, File to) {\n    return from.renameTo(to);\n  }\n\n  default long length(File file) {\n    return file.length();\n  }\n\n  default long getDirectorySize(File file) {\n    long size = 0;\n    if (file.isDirectory()) {\n      for (File f : file.listFiles()) {\n        size += getDirectorySize(f);\n      }\n    } else {\n      size = file.length();\n    }\n    return size;\n  }\n\n  default boolean deleteAll(File file) {\n    boolean result = true;\n    if (file.isDirectory()) {\n      for (File f : file.listFiles()) {\n        result = deleteAll(f) && result;\n      }\n    } else {\n      result = file.delete();\n    }\n    return result;\n  }\n\n  default boolean setLastModified(File file, long newLastModifiedTime) {\n    return file.setLastModified(newLastModifiedTime);\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/GlideJournaledLruDiskCacheWrapper.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.engine.cache.SafeKeyGenerator;\nimport com.bumptech.glide.util.Util;\nimport java.io.File;\n\n/** Implements {@link DiskCache} using {@link JournaledLruDiskCache}. */\npublic final class GlideJournaledLruDiskCacheWrapper implements DiskCache {\n  // 500 mb\n  private static final long DEFAULT_GLIDE_CACHE_SIZE_BYTES = 1024 * 1024 * 500;\n  public static final String DEFAULT_CACHE_DIR = \"glide_cache\";\n\n  public static final String KEY_VALUE_STORE_PREFIX =\n      \"com.google.android.apps.photos.diskcache.GlideJournaledLruDiskCacheWrapper\";\n\n  private final JournaledLruDiskCache diskCache;\n  private final SafeKeyGenerator safeKeyGenerator;\n  private final DiskCacheDbHelper diskCacheDbHelper;\n\n  public static GlideJournaledLruDiskCacheWrapper newInstance(Context context, File diskCacheDir) {\n    return newInstance(\n        context,\n        diskCacheDir,\n        // Default to not evicting based on entry age.\n        /* staleEvictionThresholdMs= */ Long.MAX_VALUE,\n        new DefaultClock());\n  }\n\n  public static GlideJournaledLruDiskCacheWrapper newInstance(\n      Context context, File diskCacheDir, long staleEvictionThresholdMs, Clock clock) {\n    return new GlideJournaledLruDiskCacheWrapper(\n        diskCacheDir, DiskCacheDbHelper.forProd(context), staleEvictionThresholdMs, clock);\n  }\n\n  private GlideJournaledLruDiskCacheWrapper(\n      File diskCacheDir,\n      DiskCacheDbHelper diskCacheDbHelper,\n      long staleEvictionThresholdMs,\n      Clock clock) {\n    this.diskCacheDbHelper = diskCacheDbHelper;\n    this.safeKeyGenerator = new SafeKeyGenerator();\n    this.diskCache =\n        new JournaledLruDiskCache(\n            diskCacheDir,\n            diskCacheDbHelper,\n            DEFAULT_GLIDE_CACHE_SIZE_BYTES,\n            staleEvictionThresholdMs,\n            clock);\n  }\n\n  /**\n   * Sets the maximum size of the cache to a new size in bytes.\n   *\n   * <p>Must be called on a background thread.\n   *\n   * <p>The JournaledLruDiskCache manages the sizing of the cache. Decreasing the size may schedule\n   * an eviction if the current cache size exceeds newMaximumSizeBytes. Evictions will be scheduled\n   * and executed asynchronously. Therefore, the eviction will happen based on the latest maximum\n   * cache size, not the maximum size at scheduling.\n   */\n  public void setMaximumSizeBytes(long newMaximumSizeBytes) {\n    Util.assertBackgroundThread();\n    diskCache.setMaximumSizeBytes(newMaximumSizeBytes);\n  }\n\n  @Override\n  public File get(Key key) {\n    String safeKey = safeKeyGenerator.getSafeKey(key);\n    return diskCache.get(safeKey);\n  }\n\n  @Override\n  public void put(Key key, Writer writer) {\n    String safeKey = safeKeyGenerator.getSafeKey(key);\n    File tempFile = diskCache.beginPut(safeKey);\n    // Edit already in progress, or file is already written.\n    try {\n      if (tempFile != null && writer.write(tempFile)) {\n        diskCache.commitPut(safeKey, tempFile);\n      }\n    } finally {\n      diskCache.abortPutIfNotCommitted(safeKey, tempFile);\n    }\n  }\n\n  @Override\n  public void delete(Key key) {\n    String safeKey = safeKeyGenerator.getSafeKey(key);\n    diskCache.delete(safeKey);\n  }\n\n  @Override\n  public void clear() {\n    diskCache.clear();\n  }\n\n  /**\n   * @deprecated this method will be replaced by a more specific version\n   */\n  @Deprecated\n  public SQLiteDatabase getWritableDatabase() {\n    return diskCacheDbHelper.getWritableDatabase();\n  }\n\n  /** Returns number of bytes used by the JournaledLruDiskCache currently. */\n  public long getCurrentSizeBytes() {\n    return diskCache.getCurrentSizeBytes();\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/Journal.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.DatabaseUtils;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteDoneException;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.database.sqlite.SQLiteStatement;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport com.bumptech.glide.integration.sqljournaldiskcache.SizeJournal.SizeSQLiteTransactionListener;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nfinal class Journal {\n  private static final String TAG = \"Journal\";\n\n  // You must restart the app after enabling these logs for the change to take affect.\n  // We cache isLoggable to avoid the performance hit of checking repeatedly.\n  private static final boolean LOG_VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);\n  private static final boolean LOG_DEBUG = Log.isLoggable(TAG, Log.DEBUG);\n  private static final boolean LOG_WARN = Log.isLoggable(TAG, Log.WARN);\n\n  private static final String ROW_ID = \"rowid\";\n  private static final String WHERE_KEY = JournalTable.Columns.KEY + \" = ?\";\n  private static final String WHERE_PENDING_DELETE = JournalTable.Columns.PENDING_DELETE + \" != 0\";\n\n  // If a commit fails (renameTo returns false) and then the app dies before the commit is aborted,\n  // we will end up with a temp file and an entry in the journal for a key. New puts for that key\n  // should be able to complete successfully, so we use insert or replace to allow the entry to be\n  // updated. We don't normally expect to be replacing entries.\n  private static final String INSERT_NEW_KEY_SQL =\n      \"INSERT OR REPLACE INTO \"\n          + JournalTable.TABLE_NAME\n          + \"(\"\n          + JournalTable.Columns.KEY\n          + \", \"\n          + JournalTable.Columns.LAST_MODIFIED_TIME\n          + \", \"\n          + JournalTable.Columns.SIZE\n          + \") VALUES (?, ?, ?)\";\n  private static final int INSERT_NEW_KEY_KEY_IDX = 1;\n  private static final int INSERT_NEW_KEY_MODIFIED_TIME_IDX = 2;\n  private static final int INSERT_NEW_KEY_SIZE_IDX = 3;\n\n  private static final String CONTAINS_KEY_SQL =\n      \"SELECT COUNT(*) FROM \" + JournalTable.TABLE_NAME + \" WHERE \" + WHERE_KEY;\n  private static final int CONTAINS_KEY_KEY_IDX = 1;\n\n  private static final String SELECT_ENTRY_SIZE_NOT_PENDING_SQL =\n      \"SELECT \"\n          + JournalTable.Columns.SIZE\n          + \" FROM \"\n          + JournalTable.TABLE_NAME\n          + \" WHERE \"\n          + WHERE_KEY\n          + \" AND \"\n          + JournalTable.Columns.PENDING_DELETE\n          + \" = 0\";\n  private static final int SELECT_ENTRY_SIZE_NOT_PENDING_KEY_IDX = 1;\n\n  private static final String DELETE_ENTRY_SQL =\n      \"DELETE FROM \" + JournalTable.TABLE_NAME + \" WHERE \" + WHERE_KEY;\n  private static final int DELETE_ENTRY_KEY_IDX = 1;\n\n  private static final String[] LRU_PROJECTION =\n      new String[] {JournalTable.Columns.KEY, JournalTable.Columns.SIZE};\n  private static final String[] STALE_PROJECTION =\n      new String[] {JournalTable.Columns.KEY, JournalTable.Columns.LAST_MODIFIED_TIME, ROW_ID};\n  private static final String LRU_WHERE = JournalTable.Columns.PENDING_DELETE + \" = 0\";\n  private static final String STALE_WHERE =\n      ROW_ID + \" > ? AND \" + JournalTable.Columns.LAST_MODIFIED_TIME + \" < ?\";\n  // rowid comes from https://www.sqlite.org/rowidtable.html. See b/206890186.\n  private static final String LRU_ORDER_BY =\n      JournalTable.Columns.LAST_MODIFIED_TIME + \" ASC, rowid ASC\";\n  private static final String STALE_ORDER_BY = ROW_ID + \" ASC\";\n  private static final int LRU_BATCH_SIZE = 25;\n  private static final int STALE_BATCH_SIZE = 25;\n\n  private static final String SUM_SIZE_WHERE_NOT_PENDING_DELETE =\n      \"SELECT SUM(\"\n          + JournalTable.Columns.SIZE\n          + \") FROM \"\n          + JournalTable.TABLE_NAME\n          + \" WHERE \"\n          + JournalTable.Columns.PENDING_DELETE\n          + \" = 0\";\n\n  private static final String[] PENDING_DELETE_PROJECTION = new String[] {JournalTable.Columns.KEY};\n\n  private static final int DELETE_BATCH_SIZE = 200;\n\n  private final DiskCacheDbHelper dbHelper;\n  private final SqliteStatementPool statementPool;\n  private final Clock clock;\n  private final Handler updateTimesHandler;\n  private final SizeJournal sizeJournal;\n\n  Journal(\n      DiskCacheDbHelper dbHelper,\n      Looper workThreadLooper,\n      int updateModifiedTimeBatchSize,\n      Clock clock) {\n    this.dbHelper = dbHelper;\n    this.sizeJournal = new SizeJournal(dbHelper);\n    statementPool = new SqliteStatementPool(dbHelper);\n    this.clock = clock;\n\n    updateTimesHandler =\n        new Handler(\n            workThreadLooper,\n            new UpdateTimesCallback(dbHelper, updateModifiedTimeBatchSize, clock));\n  }\n\n  long getCurrentSizeBytes() {\n    return sizeJournal.getCacheSizeBytes();\n  }\n\n  void open() {\n    sizeJournal.open();\n  }\n\n  void clear() {\n    SQLiteDatabase db = dbHelper.getWritableDatabase();\n    db.beginTransactionNonExclusive();\n    try {\n      db.delete(JournalTable.TABLE_NAME, null /*whereClause*/, null /*whereArgs*/);\n      sizeJournal.clearInTransaction();\n      db.setTransactionSuccessful();\n    } finally {\n      db.endTransaction();\n    }\n  }\n\n  void get(String key) {\n    updateTimesHandler.obtainMessage(MessageIds.ADD_LAST_MODIFIED_KEY, key).sendToTarget();\n  }\n\n  void put(String key, long sizeBytes) {\n    Preconditions.checkArgument(!TextUtils.isEmpty(key));\n    SQLiteDatabase db = dbHelper.getWritableDatabase();\n    SQLiteStatement insertStatement = statementPool.obtain(INSERT_NEW_KEY_SQL);\n    insertStatement.bindString(INSERT_NEW_KEY_KEY_IDX, key);\n    insertStatement.bindLong(INSERT_NEW_KEY_MODIFIED_TIME_IDX, clock.currentTimeMillis());\n    insertStatement.bindLong(INSERT_NEW_KEY_SIZE_IDX, sizeBytes);\n\n    SizeSQLiteTransactionListener sizeListener = sizeJournal.prepareSizeTransaction();\n    db.beginTransactionWithListenerNonExclusive(sizeListener);\n    try {\n      insertStatement.executeInsert();\n      sizeJournal.incrementSizeInTransaction(sizeListener, sizeBytes);\n      db.setTransactionSuccessful();\n    } finally {\n      db.endTransaction();\n      statementPool.offer(INSERT_NEW_KEY_SQL, insertStatement);\n      sizeJournal.endSizeTransaction(sizeListener);\n    }\n  }\n\n  List<String> getPendingDeleteKeys() {\n    List<String> result = new ArrayList<>();\n    SQLiteDatabase db = dbHelper.getReadableDatabase();\n    Cursor cursor =\n        db.query(\n            JournalTable.TABLE_NAME,\n            PENDING_DELETE_PROJECTION,\n            WHERE_PENDING_DELETE,\n            null /*selectionArgs*/,\n            null /*groupBy*/,\n            null /*having*/,\n            null /*orderBy*/);\n    try {\n      while (cursor.moveToNext()) {\n        String key = cursor.getString(cursor.getColumnIndexOrThrow(JournalTable.Columns.KEY));\n        if (TextUtils.isEmpty(key)) {\n          if (LOG_WARN) {\n            Log.w(TAG, \"Found empty or null key: %s, skipping delete: \" + key);\n          }\n        } else {\n          result.add(key);\n        }\n      }\n    } finally {\n      cursor.close();\n    }\n    return result;\n  }\n\n  List<String> getLeastRecentlyUsed(long targetByteCount) {\n    SQLiteDatabase db = dbHelper.getReadableDatabase();\n    List<String> keys = new ArrayList<>();\n    long currentByteCount = 0;\n    int currentOffset = 0;\n    boolean isOutOfEntries = false;\n    while (!isOutOfEntries && currentByteCount < targetByteCount) {\n      Cursor cursor =\n          db.query(\n              JournalTable.TABLE_NAME,\n              LRU_PROJECTION,\n              LRU_WHERE,\n              null /*selectionArgs*/,\n              null /*groupBy*/,\n              null /*having*/,\n              LRU_ORDER_BY,\n              currentOffset + \", \" + LRU_BATCH_SIZE);\n      try {\n        int keyIdx = cursor.getColumnIndexOrThrow(JournalTable.Columns.KEY);\n        int sizeIdx = cursor.getColumnIndexOrThrow(JournalTable.Columns.SIZE);\n        while (cursor.moveToNext() && currentByteCount < targetByteCount) {\n          String key = cursor.getString(keyIdx);\n          keys.add(key);\n\n          long sizeBytes = cursor.getLong(sizeIdx);\n          currentByteCount += sizeBytes;\n        }\n        isOutOfEntries = cursor.getCount() < LRU_BATCH_SIZE;\n      } finally {\n        cursor.close();\n      }\n      currentOffset += LRU_BATCH_SIZE;\n    }\n\n    // TODO(judds): for a sufficiently large file or small cache size and a failed attempt to commit\n    // a put, this can happen because our journal size will temporarily not match our File size.\n    // If this becomes an issue, we can safely just clear the cache here instead of throwing because\n    // we were about to delete all the files anyway.\n    if (isOutOfEntries && currentByteCount < targetByteCount) {\n      throw new IllegalStateException(\n          \"Size mismatch\"\n              + \", expected to be able to evict at least \"\n              + targetByteCount\n              + \" bytes\"\n              + \", but only found \"\n              + currentByteCount\n              + \" bytes worth of entries!\");\n    }\n\n    return keys;\n  }\n\n  List<String> getStaleEntries(long staleTimeThresholdMs) {\n    SQLiteDatabase db = dbHelper.getReadableDatabase();\n    List<String> keys = new ArrayList<>();\n    long currentRowId = 0L;\n    boolean isOutOfEntries = false;\n    while (!isOutOfEntries) {\n      try (Cursor cursor =\n          db.query(\n              JournalTable.TABLE_NAME,\n              STALE_PROJECTION,\n              LRU_WHERE + \" AND \" + STALE_WHERE,\n              new String[] {String.valueOf(currentRowId), String.valueOf(staleTimeThresholdMs)},\n              null /*groupBy*/,\n              null /*having*/,\n              STALE_ORDER_BY,\n              String.valueOf(STALE_BATCH_SIZE))) {\n        int keyIdx = cursor.getColumnIndexOrThrow(JournalTable.Columns.KEY);\n        while (cursor.moveToNext()) {\n          keys.add(cursor.getString(keyIdx));\n          currentRowId = cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID));\n        }\n        isOutOfEntries = cursor.getCount() < STALE_BATCH_SIZE;\n      }\n    }\n    return keys;\n  }\n\n  /**\n   * Removes the pending entry from the journal for the given key and returns the size in bytes of\n   * the entry, or returns 0 if no such entry exists.\n   */\n  void abortPut(String key) {\n    SQLiteDatabase db = dbHelper.getWritableDatabase();\n    SQLiteStatement containsKeyStatement = statementPool.obtain(CONTAINS_KEY_SQL);\n    containsKeyStatement.bindString(CONTAINS_KEY_KEY_IDX, key);\n    SQLiteStatement selectNotPendingSizeStatement =\n        statementPool.obtain(SELECT_ENTRY_SIZE_NOT_PENDING_SQL);\n    selectNotPendingSizeStatement.bindString(SELECT_ENTRY_SIZE_NOT_PENDING_KEY_IDX, key);\n    SQLiteStatement deleteEntryStatement = statementPool.obtain(DELETE_ENTRY_SQL);\n    deleteEntryStatement.bindString(DELETE_ENTRY_KEY_IDX, key);\n\n    SizeSQLiteTransactionListener sizeListener = sizeJournal.prepareSizeTransaction();\n    db.beginTransactionWithListenerNonExclusive(sizeListener);\n    try {\n      // We may be asked to abort a put that failed before the entry was updated, so we will\n      // occasionally fail to find the entry here.\n      boolean isKeyPresent = 0 != containsKeyStatement.simpleQueryForLong();\n      if (!isKeyPresent) {\n        return;\n      }\n      long entrySize;\n      try {\n        entrySize = selectNotPendingSizeStatement.simpleQueryForLong();\n      } catch (SQLiteDoneException e) {\n        // No row found for this key that is not pending delete.\n        entrySize = 0;\n      }\n      int deleted = deleteEntryStatement.executeUpdateDelete();\n      if (deleted != 1) {\n        throw new IllegalStateException(\n            \"Failed to delete entry\"\n                + \", key: \"\n                + key\n                + \", size: \"\n                + entrySize\n                + \", actually deleted: \"\n                + deleted);\n      } else {\n        // If the item is pending delete its size is 0 here - skip decrementing.\n        if (entrySize != 0) {\n          sizeJournal.decrementSizeInTransaction(sizeListener, entrySize);\n        }\n      }\n      db.setTransactionSuccessful();\n    } finally {\n      db.endTransaction();\n      statementPool.offer(CONTAINS_KEY_SQL, containsKeyStatement);\n      statementPool.offer(SELECT_ENTRY_SIZE_NOT_PENDING_SQL, selectNotPendingSizeStatement);\n      statementPool.offer(DELETE_ENTRY_SQL, deleteEntryStatement);\n      sizeJournal.endSizeTransaction(sizeListener);\n    }\n  }\n\n  void delete(List<String> keys) {\n    SQLiteDatabase db = dbHelper.getWritableDatabase();\n    for (int startPosition = 0; startPosition < keys.size(); startPosition += DELETE_BATCH_SIZE) {\n      int endPosition = Math.min(keys.size(), startPosition + DELETE_BATCH_SIZE);\n      List<String> batch = keys.subList(startPosition, endPosition);\n      int batchSize = batch.size();\n      if (batchSize == 0) {\n        if (LOG_WARN) {\n          Log.w(\n              TAG,\n              \"Unexpectedly 0 sized batch between: \"\n                  + startPosition\n                  + \" and endPosition: \"\n                  + endPosition);\n        }\n        continue;\n      }\n      String[] keysToDelete = batch.toArray(new String[batchSize]);\n      db.beginTransactionNonExclusive();\n      try {\n        int deleted =\n            db.delete(JournalTable.TABLE_NAME, buildKeySelectionSet(batchSize), keysToDelete);\n        if (deleted != keysToDelete.length && LOG_WARN) {\n          Log.w(\n              TAG,\n              \"Failed to delete all expected entries\"\n                  + \", expected: \"\n                  + keysToDelete.length\n                  + \", deleted: \"\n                  + deleted);\n        }\n        db.setTransactionSuccessful();\n      } finally {\n        db.endTransaction();\n      }\n    }\n  }\n\n  private static String buildKeySelectionSet(int count) {\n    Preconditions.checkArgument(count > 0);\n    String prefix = \" IN(\";\n    String postfix = \"?)\";\n    // (?,?,?), so 2 characters per item, except for the last one which is one character.\n    int commaSeparatedCount = count - 1;\n    StringBuilder sb =\n        new StringBuilder(\n                JournalTable.Columns.KEY.length()\n                    + prefix.length()\n                    + (2 * commaSeparatedCount)\n                    + postfix.length())\n            .append(JournalTable.Columns.KEY)\n            .append(prefix);\n    for (int i = 0; i < commaSeparatedCount; i++) {\n      sb.append(\"?,\");\n    }\n    return sb.append(postfix).toString();\n  }\n\n  void markPendingDelete(List<String> keys) {\n    ContentValues values = new ContentValues();\n    values.put(JournalTable.Columns.PENDING_DELETE, 1);\n    SQLiteDatabase db = dbHelper.getWritableDatabase();\n\n    for (int startPosition = 0; startPosition < keys.size(); startPosition += DELETE_BATCH_SIZE) {\n      int endPosition = Math.min(keys.size(), startPosition + DELETE_BATCH_SIZE);\n      List<String> batch = keys.subList(startPosition, endPosition);\n      int batchSize = batch.size();\n\n      String[] keysToDelete = batch.toArray(new String[batchSize]);\n      String keySelectionSet = buildKeySelectionSet(batchSize);\n      SizeSQLiteTransactionListener sizeListener = sizeJournal.prepareSizeTransaction();\n      db.beginTransactionWithListenerNonExclusive(sizeListener);\n      try {\n        long sumOfSizesOfNewlyPendingEntries =\n            DatabaseUtils.longForQuery(\n                db, SUM_SIZE_WHERE_NOT_PENDING_DELETE + \" AND \" + keySelectionSet, keysToDelete);\n        sizeJournal.decrementSizeInTransaction(sizeListener, sumOfSizesOfNewlyPendingEntries);\n        db.update(JournalTable.TABLE_NAME, values, keySelectionSet, keysToDelete);\n        db.setTransactionSuccessful();\n      } finally {\n        db.endTransaction();\n        sizeJournal.endSizeTransaction(sizeListener);\n      }\n    }\n  }\n\n  private static class UpdateTimesCallback implements Handler.Callback {\n    private final SQLiteOpenHelper dbHelper;\n    private final int updateModifiedTimeBatchSize;\n    private final List<String> keysToUpdate;\n    private final String batchUpdatedModifiedTimeSql;\n    private final Clock clock;\n\n    private SQLiteStatement sqlStatement;\n\n    UpdateTimesCallback(SQLiteOpenHelper dbHelper, int updateModifiedTimeBatchSize, Clock clock) {\n      this.clock = clock;\n      Preconditions.checkArgument(updateModifiedTimeBatchSize > 0);\n      this.dbHelper = dbHelper;\n      this.updateModifiedTimeBatchSize = updateModifiedTimeBatchSize;\n      keysToUpdate = new ArrayList<>(updateModifiedTimeBatchSize);\n\n      batchUpdatedModifiedTimeSql =\n          \"UPDATE \"\n              + JournalTable.TABLE_NAME\n              + \" SET \"\n              + JournalTable.Columns.LAST_MODIFIED_TIME\n              + \" = ?\"\n              + \" WHERE \"\n              + buildKeySelectionSet(updateModifiedTimeBatchSize);\n    }\n\n    private SQLiteStatement getSqlStatement() {\n      if (sqlStatement == null) {\n        sqlStatement = dbHelper.getWritableDatabase().compileStatement(batchUpdatedModifiedTimeSql);\n      }\n      return sqlStatement;\n    }\n\n    private void updateTimes() {\n      long startTime = clock.currentTimeMillis();\n      SQLiteDatabase db = dbHelper.getWritableDatabase();\n      SQLiteStatement statement = getSqlStatement();\n      long modifiedTime = clock.currentTimeMillis();\n      statement.bindLong(1, modifiedTime);\n      int size = keysToUpdate.size();\n      for (int i = 0; i < size; i++) {\n        String key = keysToUpdate.get(i);\n        // 1 indexed, with the modified time as the first argument.\n        statement.bindString(i + 2, key);\n      }\n      db.beginTransactionNonExclusive();\n      try {\n        int updated = statement.executeUpdateDelete();\n        if (updated != updateModifiedTimeBatchSize && LOG_DEBUG) {\n          Set<String> uniqueKeys = new HashSet<>(keysToUpdate);\n          // This can happen in one of two cases:\n          // 1. Files are deleted out from under us (by the system), triggering a cache rebuild.\n          // 2. The corresponding entries are evicted while they're in the get queue.\n          Log.d(\n              TAG,\n              \"Failed to update modified time for all rows\"\n                  + \", time: \"\n                  + modifiedTime\n                  + \", expected: \"\n                  + updateModifiedTimeBatchSize\n                  + \", actually updated: \"\n                  + updated\n                  + \", unique keys: \"\n                  + uniqueKeys.size());\n        }\n        db.setTransactionSuccessful();\n      } finally {\n        db.endTransaction();\n      }\n\n      if (LOG_VERBOSE) {\n        Log.v(\n            TAG,\n            \"Completed update times with \"\n                + keysToUpdate.size()\n                + \" updates in \"\n                + (clock.currentTimeMillis() - startTime));\n      }\n    }\n\n    @Override\n    public boolean handleMessage(Message msg) {\n      if (msg.what != MessageIds.ADD_LAST_MODIFIED_KEY) {\n        return false;\n      }\n      String updatedKey = (String) msg.obj;\n      if (!keysToUpdate.contains(updatedKey)) {\n        keysToUpdate.add(updatedKey);\n      }\n      if (keysToUpdate.size() == updateModifiedTimeBatchSize) {\n        updateTimes();\n        keysToUpdate.clear();\n      }\n\n      return true;\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/JournalTable.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nfinal class JournalTable {\n  static final String TABLE_NAME = \"journal\";\n  private static final String INDEX_TIMESTAMP_KEY = \"journal_timestamp_key_idx\";\n\n  interface Columns {\n    /** The cache key/cache file name. */\n    String KEY = \"key\";\n\n    /** The time the key was most recently created, updated, or read in UTC milliseconds. */\n    String LAST_MODIFIED_TIME = \"last_modified_time\";\n\n    /** 1 if the key is going to be deleted, 0 otherwise. */\n    String PENDING_DELETE = \"pending_delete\";\n\n    /** The length in bytes of the cache file. */\n    String SIZE = \"size\";\n  }\n\n  static String getSqlCreateStatement() {\n    return \"CREATE TABLE \"\n        + TABLE_NAME\n        + \" (\"\n        + Columns.KEY\n        + \" STRING PRIMARY KEY, \"\n        + Columns.LAST_MODIFIED_TIME\n        + \" INTEGER NOT NULL, \"\n        + Columns.PENDING_DELETE\n        + \" INTEGER NOT NULL DEFAULT 0, \"\n        + Columns.SIZE\n        + \" INTEGER NOT NULL\"\n        + \")\";\n  }\n\n  static String getIndexString() {\n    return \"CREATE INDEX \"\n        + INDEX_TIMESTAMP_KEY\n        + \" ON \"\n        + TABLE_NAME\n        + \" (\"\n        + Columns.LAST_MODIFIED_TIME\n        + \", \"\n        + Columns.KEY\n        + \")\";\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/JournaledLruDiskCache.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.os.Process;\nimport android.util.Log;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * An Lru disk cache that stores each entry as a single File and uses a SQL based journal to track\n * sizes and eviction order.\n *\n * <p>Operations are not guaranteed and will silently fail in unexpected cases.\n *\n * <p>The size of the cache is approximate and will be exceeded for short periods of time. Failure\n * cases may leave behind temporary files that should be cleaned up in the future when the cache is\n * re-opened or when operations are attempted again.\n *\n * <p>This class is thread safe and may be accessed from multiple threads simultaneously.\n */\nfinal class JournaledLruDiskCache {\n  private static final String TAG = \"DiskCache\";\n  private static final String CANARY_FILE_NAME = \"cache_canary\";\n  // You must restart the app after enabling these logs for the change to take affect.\n  // We cache isLoggable to avoid the performance hit of checking repeatedly.\n  private static final boolean LOG_WARN = Log.isLoggable(TAG, Log.WARN);\n  private static final boolean LOG_VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);\n  // The fraction of the maximum byte size of the cache we will allow the cache to go over before\n  // triggering an eviction.\n  private static final float DEFAULT_EVICTION_SLOP_MULTIPLIER = 0.05f;\n  // The number of items we will queue to update the date modified time of in batches.\n  private static final int DEFAULT_UPDATE_MODIFIED_TIME_BATCH_SIZE = 20;\n\n  static final String TEMP_FILE_INDICATOR = \".tmp\";\n\n  private final File cacheDirectory;\n  private final FileSystem fileSystem;\n  private final Journal journal;\n  // We use this File to determine if the system has wiped out our cache directory, which it may do\n  // at any time. If the File is not present, then either we've never opened the cache for the given\n  // directory before, or the cache was wiped.\n  private final File canaryFile;\n  private final EvictionManager evictionManager;\n  private final RecoveryManager recoveryManager;\n  private final EntryCache entries = new EntryCache();\n\n  private volatile boolean isOpen;\n\n  /**\n   * @param cacheDirectory The directory in which the cache should store its files (Warning: the\n   *     cache will delete all Files in the given directory. The directory should not be used to\n   *     store any other content).\n   * @param maximumSizeBytes The target maximum size in bytes. The cache size may briefly exceed\n   *     this size by up to around 25mb depending on the size, thread scheduling, and the number of\n   *     failed requests.\n   */\n  JournaledLruDiskCache(\n      File cacheDirectory,\n      DiskCacheDbHelper diskCacheDbHelper,\n      long maximumSizeBytes,\n      long staleEvictionThresholdMs,\n      Clock clock) {\n    this(\n        cacheDirectory,\n        diskCacheDbHelper,\n        new FileSystem() {},\n        maximumSizeBytes,\n        getBackgroundLooper(),\n        DEFAULT_EVICTION_SLOP_MULTIPLIER,\n        DEFAULT_UPDATE_MODIFIED_TIME_BATCH_SIZE,\n        staleEvictionThresholdMs,\n        clock);\n  }\n\n  @VisibleForTesting\n  JournaledLruDiskCache(\n      File cacheDirectory,\n      DiskCacheDbHelper diskCacheDbHelper,\n      FileSystem fileSystem,\n      long maximumSizeBytes,\n      Looper workLooper,\n      float slopMultiplier,\n      int updateModifiedTimeBatchSize,\n      long staleEvictionThresholdMs,\n      Clock clock) {\n    Preconditions.checkArgument(\n        updateModifiedTimeBatchSize >= 1, \"updated modified time batch size must be >= 1\");\n    this.cacheDirectory = cacheDirectory;\n    this.fileSystem = fileSystem;\n\n    journal = new Journal(diskCacheDbHelper, workLooper, updateModifiedTimeBatchSize, clock);\n    canaryFile = new File(cacheDirectory, CANARY_FILE_NAME);\n\n    evictionManager =\n        new EvictionManager(\n            this,\n            cacheDirectory,\n            fileSystem,\n            journal,\n            workLooper,\n            maximumSizeBytes,\n            slopMultiplier,\n            staleEvictionThresholdMs,\n            clock);\n    recoveryManager = new RecoveryManager(this, cacheDirectory, journal, workLooper);\n  }\n\n  private static Looper getBackgroundLooper() {\n    HandlerThread workThread =\n        new HandlerThread(\"disk_cache_journal\", Process.THREAD_PRIORITY_BACKGROUND);\n    workThread.start();\n    return workThread.getLooper();\n  }\n\n  @SuppressWarnings(\"checkstyle:UnnecessaryParentheses\") // Readability\n  private void openIfNotOpen() {\n    if (!isOpen) {\n      synchronized (this) {\n        if (!isOpen) {\n          boolean createdDirectory =\n              cacheDirectory.mkdirs() || (cacheDirectory.exists() && cacheDirectory.isDirectory());\n          if (!createdDirectory) {\n            throw new IllegalStateException(\"Failed to create cache directory: \" + cacheDirectory);\n          }\n          journal.open();\n          isOpen = true;\n          recoveryManager.triggerRecovery();\n        }\n      }\n    }\n  }\n\n  // TODO(judds): rather than polling, we should use Android's FileObserver.\n  private void verifyCanaryOrClear() {\n    if (fileSystem.exists(canaryFile)) {\n      return;\n    }\n\n    synchronized (this) {\n      if (fileSystem.exists(canaryFile)) {\n        return;\n      }\n      if (LOG_WARN) {\n        Log.w(TAG, \"Failed to find canary file, clearing disk cache\");\n      }\n      clear();\n    }\n  }\n\n  private void touchCanaryFile() {\n    try {\n      if (!fileSystem.createNewFile(canaryFile) && LOG_WARN) {\n        Log.w(TAG, \"Failed to create new canary file\");\n      }\n    } catch (IOException e) {\n      if (LOG_WARN) {\n        Log.w(TAG, \"Threw creating canary\", e);\n      }\n    }\n  }\n\n  long getCurrentSizeBytes() {\n    return journal.getCurrentSizeBytes();\n  }\n\n  /**\n   * Makes a best effort attempt to delete all Files and clear the journal.\n   *\n   * <p>In progress writes may still complete and/or leave behind partial data.\n   */\n  public synchronized void clear() {\n    if (LOG_WARN) {\n      Log.w(TAG, \"Clearing cache and deleting all entries!\");\n    }\n    fileSystem.deleteAll(cacheDirectory);\n    journal.clear();\n    isOpen = false;\n    entries.clear();\n    openIfNotOpen();\n    touchCanaryFile();\n  }\n\n  /**\n   * Attempts to delete any content currently in the cache for the given key.\n   *\n   * <p>If no entry for the given key is found, this method will silently fail. If an entry is\n   * found, it is possible the File deletion will fail and be re-attempted in the future.\n   */\n  public void delete(String key) {\n    delete(Collections.singletonList(key));\n  }\n\n  List<String> delete(List<String> keys) {\n    journal.markPendingDelete(keys);\n    List<String> successfullyDeleted = new ArrayList<>(keys.size());\n    for (String key : keys) {\n      EntryCache.Entry entry = entries.get(key);\n      entry.acquireWriteLock();\n      try {\n        File file = getCacheFile(key);\n        if (fileSystem.delete(file)) {\n          successfullyDeleted.add(key);\n        } else if (LOG_WARN) {\n          Log.w(TAG, \"Failed to delete file: \" + file);\n        }\n        entry.setNotPresent();\n      } finally {\n        entry.releaseWriteLock();\n      }\n    }\n    journal.delete(successfullyDeleted);\n    return successfullyDeleted;\n  }\n\n  /**\n   * Returns a File committed previously for the given key, or {@code null} if no such File exists.\n   *\n   * <p>If a write is in progress but not yet committed for the given key, this method will return\n   * {@code null} immediately, just as if the key were simply not present.\n   */\n  public File get(String key) {\n    long startTime = getLogTime();\n    openIfNotOpen();\n    final File result;\n    EntryCache.Entry entry = entries.get(key);\n    entry.acquireReadLock();\n    try {\n      if (entry.isStateKnown()) {\n        result = entry.isPresent() ? entry.getFile() : null;\n      } else {\n        File cacheFile = getCacheFile(key);\n        if (fileSystem.exists(cacheFile)) {\n          entry.setPresent(cacheFile);\n          result = cacheFile;\n        } else {\n          entry.setNotPresent();\n          result = null;\n        }\n      }\n      if (result != null) {\n        journal.get(key);\n      }\n\n      if (LOG_VERBOSE) {\n        Log.v(TAG, \"Completed get in: \" + getElapsedTime(startTime) + \", key: \" + key);\n      }\n    } finally {\n      entry.releaseReadLock();\n    }\n\n    return result;\n  }\n\n  /**\n   * Starts a put for the given key and returns a temporary {@link File} to which the caller can\n   * write data, or {@code null} if an edit is already in progress for the given Key, or if a\n   * committed entry already exists for the given key.\n   *\n   * <p>Callers should call {@link #commitPut(String, File)} with the given key and the {@link File}\n   * returned from this method after they finish writing data to make the data they have written\n   * available to calls to {@link #get(String)}. If an error occurs while writing data, callers can\n   * omit calling {@link #commitPut(String, File)} and use {@link #abortPutIfNotCommitted(String,\n   * File)} to cleanup any partial {@link File Files}.\n   *\n   * <p>Callers must call {@link #abortPutIfNotCommitted(String, File)} regardless of whether or not\n   * their write succeeds. The expected pattern is as follows:\n   *\n   * <pre>{@code\n   * File tempFile = cache.beginPut(key);\n   * try {\n   *   if (tempFile != null && writeToFile(someData, tempFile)) {\n   *    cache.commitPut(key, tempFile);\n   *   }\n   * } finally {\n   *   cache.abortIfNotCommitted(key, tempFile);\n   * }\n   * }</pre>\n   *\n   * <p>Until the caller calls {@link #abortPutIfNotCommitted(String, File)}, a lock is held that\n   * will block future calls to this method for the given key.\n   *\n   * <p>The returned {@link File} may contain partial data if a previous write to this key failed.\n   * Callers should not assume it is safe to append to the File without first clearing it.\n   */\n  @Nullable\n  public File beginPut(String key) {\n    long startTime = getLogTime();\n    openIfNotOpen();\n    verifyCanaryOrClear();\n    EntryCache.Entry entry = entries.get(key);\n    entry.acquireWriteLock();\n\n    File permanentFile = getCacheFile(key);\n    if (fileSystem.exists(permanentFile)) {\n      return null;\n    }\n\n    File result = getTempFile(key);\n    if (LOG_VERBOSE) {\n      Log.v(TAG, \"Completed begin put in: \" + getElapsedTime(startTime) + \", key: \" + key);\n    }\n    return result;\n  }\n\n  /**\n   * Updates the size of the cache based on the data in the given temporary file and renames the\n   * given temporary File to its permanent equivalent and makes it available to calls from {@link\n   * #get(String)}.\n   *\n   * <p>The given {@link File} must be a {@link File} returned from {@link #get(String)} for the\n   * given key. No validation is performed to verify either that the given {@link File} is a\n   * legitimate temporary file from this cache or that the given {@link File} matches the given key.\n   *\n   * <p>It is possible this commit may fail silently, there is no guarantee that the data in the\n   * given {@link File} will actually be available from {@link #get(String)}} when this method\n   * completes. In practice commits should fail rarely unless insufficient storage is available or\n   * the cache's directory or files are manipulated by a third party.\n   *\n   * <p>If the commit does fail, it will do so in one of two ways:\n   *\n   * <ul>\n   *   <li>Prior to or while writing the entry to the journal\n   *   <li>After writing the entry to the journal prior to or while renaming the temporary file to\n   *       the permanent file.\n   * </ul>\n   *\n   * If the commit fails prior to writing the entry to the journal, the dangling temporary File will\n   * be found during recovery and deleted. If the commit fails after writing the entry to the\n   * journal, the temporary file will be found during recovery and deleted and the corresponding\n   * journal entry will also be deleted. The absence of a temporary File for a given key is assumed\n   * to mean that either no entry exists, or the entry is committed and may be read.\n   *\n   * @throws IllegalStateException If this method wasn't preceded by a call to {@link\n   *     #beginPut(String)} for the given key.\n   */\n  public void commitPut(String key, File temp) {\n    long startTime = getLogTime();\n\n    long totalBytesAdded = fileSystem.length(temp);\n    journal.put(key, totalBytesAdded);\n\n    if (LOG_VERBOSE) {\n      Log.v(TAG, \"Completed insertIntoDb in: \" + getElapsedTime(startTime));\n    }\n\n    long startRenameTime = getLogTime();\n    File permanentFile = getCacheFile(key);\n\n    boolean isRenameSuccessful = fileSystem.rename(temp, permanentFile);\n    // If we fail to rename the file, we will try to recover in our next recovery phase.\n    if (isRenameSuccessful) {\n      if (LOG_VERBOSE) {\n        Log.v(TAG, \"Successfully renamed in: \" + getElapsedTime(startRenameTime));\n      }\n      EntryCache.Entry entry = entries.get(key);\n      entry.setPresent(permanentFile);\n    } else if (LOG_WARN) {\n      Log.w(TAG, \"Failed to rename file\" + \", from: \" + temp + \", to: \" + permanentFile);\n    }\n\n    evictionManager.maybeScheduleEviction();\n\n    if (LOG_VERBOSE) {\n      Log.v(\n          TAG,\n          \"Completed commitPut in: \"\n              + getElapsedTime(startTime)\n              + \", current size: \"\n              + journal.getCurrentSizeBytes()\n              + \", key: \"\n              + key);\n    }\n  }\n\n  /**\n   * Releases the write lock for the given key and, if the write was not committed, cleans up the\n   * given temporary File and the corresponding journal entry for the given Key.\n   *\n   * <p>A write is assumed to have not been committed if the given temporary File still exists.\n   */\n  public void abortPutIfNotCommitted(String key, File temp) {\n    try {\n      // If the temporary File still exists, we haven't committed. If it doesn't exist, we either\n      // didn't start writing and have nothing to roll back, or we finished writing and finished\n      // the rename so the edit is committed.\n      if (temp != null && fileSystem.delete(temp)) {\n        journal.abortPut(key);\n        EntryCache.Entry entry = entries.get(key);\n        entry.setUnknown();\n      }\n    } finally {\n      EntryCache.Entry entry = entries.get(key);\n      entry.releaseWriteLock();\n    }\n  }\n\n  void recoverPartialWrite(File temp) {\n    String key = keyFromFile(temp);\n    EntryCache.Entry entry = entries.get(key);\n    entry.acquireWriteLock();\n    try {\n      // Try to delete the temporary file, if it fails, we will try again in the next recovery\n      // phase.\n      boolean deleted = temp.delete();\n      if (!deleted) {\n        if (LOG_WARN) {\n          Log.w(TAG, \"Failed to cleanup in progress write: \" + temp);\n        }\n        // The write lock prevents us from directly racing with an in progress write. However when\n        // the write lock is released, we will get to run. If the write completed successfully,\n        // the\n        // temp file will no longer exist, but the entry will. We do not want to delete the entry\n        // just because we happened to try to run recovery during the write.\n        return;\n      }\n      delete(key);\n    } finally {\n      entry.releaseWriteLock();\n    }\n  }\n\n  private String keyFromFile(File file) {\n    String name = file.getName();\n    final String key;\n    if (name.endsWith(TEMP_FILE_INDICATOR)) {\n      key = name.substring(0, name.length() - TEMP_FILE_INDICATOR.length());\n    } else {\n      key = name;\n    }\n    return key;\n  }\n\n  /**\n   * Sets the maximum size of the cache to a new size in bytes.\n   *\n   * <p>Must be called on a background thread.\n   *\n   * <p>The EvictionManager manages the sizing of the cache. Decreasing the size may schedule an\n   * eviction if the current cache size exceeds newMaximumSizeBytes. Evictions will be scheduled and\n   * executed asynchronously. Therefore, the eviction will happen based on the latest maximum cache\n   * size, not the maximum size at scheduling.\n   */\n  public void setMaximumSizeBytes(long newMaximumSizeBytes) {\n    evictionManager.setMaximumSizeBytes(newMaximumSizeBytes);\n  }\n\n  private File getCacheFile(String key) {\n    return new File(cacheDirectory, key);\n  }\n\n  private File getTempFile(String key) {\n    return new File(cacheDirectory, key + TEMP_FILE_INDICATOR);\n  }\n\n  private static long getLogTime() {\n    return System.currentTimeMillis();\n  }\n\n  private static long getElapsedTime(long startTime) {\n    return getLogTime() - startTime;\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/MessageIds.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\n/**\n * A unique set of non-zero message ids to use when requesting that work be done on the disk cache's\n * background thread.\n */\ninterface MessageIds {\n  int ADD_LAST_MODIFIED_KEY = 1;\n  int EVICT = 2;\n  int RECOVER = 3;\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/RecoveryManager.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.util.List;\n\n/** Finds and cleans up failed writes and deletes on the work thread. */\nfinal class RecoveryManager {\n\n  private final JournaledLruDiskCache diskCache;\n  private final Journal journal;\n  private final File diskCacheDir;\n  private final Looper workLooper;\n  private final Handler recoveryHandler;\n\n  RecoveryManager(\n      JournaledLruDiskCache diskCache, File diskCacheDir, Journal journal, Looper workLooper) {\n    this.diskCache = diskCache;\n    this.journal = journal;\n    this.diskCacheDir = diskCacheDir;\n    this.workLooper = workLooper;\n\n    recoveryHandler = new Handler(workLooper, new RecoveryCallback());\n  }\n\n  void triggerRecovery() {\n    recoveryHandler.obtainMessage(MessageIds.RECOVER).sendToTarget();\n  }\n\n  private void runRecoveryOnWorkThread() {\n    if (!Looper.myLooper().equals(workLooper)) {\n      throw new IllegalStateException(\n          \"Cannot run recovery on a thread other than the work\" + \" thread!\");\n    }\n    recoverPartialWrites();\n    recoverPartialDeletes();\n  }\n\n  private void recoverPartialDeletes() {\n    List<String> pendingDeleteKeys = journal.getPendingDeleteKeys();\n    diskCache.delete(pendingDeleteKeys);\n  }\n\n  private void recoverPartialWrites() {\n    File[] partialWrites =\n        diskCacheDir.listFiles(\n            new FilenameFilter() {\n              @Override\n              public boolean accept(File dir, String filename) {\n                return filename.endsWith(JournaledLruDiskCache.TEMP_FILE_INDICATOR);\n              }\n            });\n    if (partialWrites != null) {\n      for (File file : partialWrites) {\n        diskCache.recoverPartialWrite(file);\n      }\n    }\n  }\n\n  private class RecoveryCallback implements Handler.Callback {\n\n    @Override\n    public boolean handleMessage(Message msg) {\n      if (msg.what != MessageIds.RECOVER) {\n        return false;\n      }\n      runRecoveryOnWorkThread();\n      return true;\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/SizeJournal.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport android.content.ContentValues;\nimport android.database.DatabaseUtils;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteStatement;\nimport android.database.sqlite.SQLiteTransactionListener;\nimport androidx.annotation.NonNull;\nimport androidx.core.util.Pools.Pool;\nimport com.bumptech.glide.util.pool.FactoryPools;\nimport com.bumptech.glide.util.pool.FactoryPools.Poolable;\nimport com.bumptech.glide.util.pool.FactoryPools.Resetter;\nimport com.bumptech.glide.util.pool.StateVerifier;\nimport java.util.concurrent.atomic.AtomicLong;\n\nfinal class SizeJournal {\n  private static final String UPDATE_CACHE_SIZE_SQL =\n      \"UPDATE \"\n          + SizeTable.TABLE_NAME\n          + \" SET \"\n          + SizeTable.Columns.SIZE\n          + \" = \"\n          + SizeTable.Columns.SIZE\n          + \" + ?\";\n  private static final int UPDATE_CACHE_SIZE_SIZE_INCREMENT_IDX = 1;\n\n  private static final String CONTAINS_SIZE_QUERY = \"SELECT COUNT(*) FROM \" + SizeTable.TABLE_NAME;\n  private static final String CACHE_SIZE_QUERY =\n      \"SELECT \" + SizeTable.Columns.SIZE + \" FROM \" + SizeTable.TABLE_NAME;\n  private final AtomicLong size = new AtomicLong();\n  private final SqliteStatementPool updateCacheSizePool;\n  private final Pool<SizeSQLiteTransactionListener> sizeListenerPool =\n      FactoryPools.threadSafe(\n          /* size= */ 20,\n          new FactoryPools.Factory<SizeSQLiteTransactionListener>() {\n            @Override\n            public SizeSQLiteTransactionListener create() {\n              return new SizeSQLiteTransactionListener();\n            }\n          },\n          new Resetter<SizeSQLiteTransactionListener>() {\n            @Override\n            public void reset(@NonNull SizeSQLiteTransactionListener object) {\n              object.clear();\n            }\n          });\n\n  private final DiskCacheDbHelper dbHelper;\n\n  SizeJournal(DiskCacheDbHelper dbHelper) {\n    this.dbHelper = dbHelper;\n    updateCacheSizePool = new SqliteStatementPool(dbHelper);\n  }\n\n  void open() {\n    SQLiteDatabase db = dbHelper.getReadableDatabase();\n    boolean containsSize =\n        0 != DatabaseUtils.longForQuery(db, CONTAINS_SIZE_QUERY, null /*selectionArgs*/);\n    final long currentSize;\n    if (!containsSize) {\n      ContentValues values = new ContentValues();\n      values.put(SizeTable.Columns.SIZE, 0);\n      db.insert(SizeTable.TABLE_NAME, null /*nullColumnHack*/, values);\n      currentSize = 0;\n    } else {\n      currentSize = DatabaseUtils.longForQuery(db, CACHE_SIZE_QUERY, null /*selectionArgs*/);\n    }\n    size.set(currentSize);\n  }\n\n  void clearInTransaction() {\n    SQLiteDatabase db = dbHelper.getReadableDatabase();\n    db.delete(SizeTable.TABLE_NAME, null /*whereClause*/, null /*whereArgs*/);\n    size.set(0);\n  }\n\n  long getCacheSizeBytes() {\n    return size.get();\n  }\n\n  SizeSQLiteTransactionListener prepareSizeTransaction() {\n    SizeSQLiteTransactionListener result = sizeListenerPool.acquire();\n    if (result == null) {\n      result = new SizeSQLiteTransactionListener();\n    }\n    return result;\n  }\n\n  void endSizeTransaction(SizeSQLiteTransactionListener listener) {\n    listener.clear();\n    sizeListenerPool.release(listener);\n  }\n\n  void decrementSizeInTransaction(SizeSQLiteTransactionListener sizeListener, long decrementBy) {\n    incrementSizeInTransaction(sizeListener, -decrementBy);\n  }\n\n  void incrementSizeInTransaction(SizeSQLiteTransactionListener sizeListener, long incrementBy) {\n    sizeListener.updatedSize = incrementBy;\n\n    SQLiteStatement updateCacheSizeStatement = updateCacheSizePool.obtain(UPDATE_CACHE_SIZE_SQL);\n    try {\n      updateCacheSizeStatement.bindLong(UPDATE_CACHE_SIZE_SIZE_INCREMENT_IDX, incrementBy);\n      updateCacheSizeStatement.executeUpdateDelete();\n      size.addAndGet(incrementBy);\n    } finally {\n      updateCacheSizePool.offer(UPDATE_CACHE_SIZE_SQL, updateCacheSizeStatement);\n    }\n  }\n\n  /** A listener that reverts size changes upon transaction failure. */\n  final class SizeSQLiteTransactionListener implements SQLiteTransactionListener, Poolable {\n    private long updatedSize;\n\n    void clear() {\n      updatedSize = 0;\n    }\n\n    @Override\n    public void onBegin() {}\n\n    @Override\n    public void onCommit() {}\n\n    @Override\n    public void onRollback() {\n      // Revert the increment of size on transaction failure.\n      size.addAndGet(-updatedSize);\n    }\n\n    @NonNull\n    @Override\n    public StateVerifier getVerifier() {\n      return StateVerifier.newInstance();\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/SizeTable.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nfinal class SizeTable {\n  static final String TABLE_NAME = \"size\";\n\n  interface Columns {\n    String ID = \"id\";\n\n    /** The total size in bytes of all files in the cache (+- pending deletes and inserts). */\n    String SIZE = \"size\";\n  }\n\n  static String getSqlCreateStatement() {\n    return \"CREATE TABLE \"\n        + TABLE_NAME\n        + \" (\"\n        + Columns.ID\n        + \" INTEGER PRIMARY KEY, \"\n        + Columns.SIZE\n        + \" INTEGER NOT NULL DEFAULT 0\"\n        + \")\";\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/SqliteStatementPool.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.database.sqlite.SQLiteStatement;\nimport java.util.ArrayDeque;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Queue;\n\nfinal class SqliteStatementPool {\n  private static final int MAX_SIZE = 10;\n\n  private final Map<String, Queue<SQLiteStatement>> pool = new HashMap<>();\n  private final SQLiteOpenHelper dbHelper;\n\n  SqliteStatementPool(SQLiteOpenHelper dbHelper) {\n    this.dbHelper = dbHelper;\n  }\n\n  SQLiteStatement obtain(String sql) {\n    SQLiteStatement statement = null;\n    synchronized (pool) {\n      Queue<SQLiteStatement> queueForSql = pool.get(sql);\n      if (queueForSql != null) {\n        statement = queueForSql.poll();\n      }\n    }\n    if (statement == null) {\n      statement = dbHelper.getWritableDatabase().compileStatement(sql);\n    }\n    return statement;\n  }\n\n  void offer(String sql, SQLiteStatement statement) {\n    statement.clearBindings();\n    synchronized (pool) {\n      Queue<SQLiteStatement> queueForSql = pool.get(sql);\n      if (queueForSql == null) {\n        queueForSql = new ArrayDeque<>(MAX_SIZE);\n        pool.put(sql, queueForSql);\n      }\n      if (queueForSql.size() < MAX_SIZE) {\n        queueForSql.offer(statement);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/test/java/com/bumptech/glide/integration/sqljournaldiskcache/DiskCacheDbHelperUpgradeTest.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.content.Context;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class DiskCacheDbHelperUpgradeTest {\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Test\n  public void onUpgrade_fromVersionOneToTwo_producesFunctionalTablesAndColumns()\n      throws IOException {\n    try (DiskCacheDbHelper versionOneHelper =\n        new DiskCacheDbHelper(context, /* isInMemory= */ false, /* databaseVersion= */ 1)) {\n      versionOneHelper.getWritableDatabase();\n    }\n\n    try (DiskCacheDbHelper versionTwoHelper =\n        new DiskCacheDbHelper(context, /* isInMemory= */ false, /* databaseVersion= */ 2)) {\n      versionTwoHelper.getWritableDatabase();\n    }\n\n    ensureWeCanReadFromDiskCache();\n  }\n\n  // A poor mans way of ensuring that we can read from the various sqlite tables in the way we\n  // expect.\n  private void ensureWeCanReadFromDiskCache() throws IOException {\n    try (DiskCacheDbHelper diskCacheDbHelper = DiskCacheDbHelper.forProd(context)) {\n      JournaledLruDiskCache diskCache =\n          new JournaledLruDiskCache(\n              context.getCacheDir(),\n              diskCacheDbHelper,\n              /* maximumSizeBytes= */ Long.MAX_VALUE,\n              /* staleEvictionThresholdMs= */ Long.MAX_VALUE,\n              new DefaultClock());\n\n      String key = \"key\";\n      File file = diskCache.beginPut(key);\n      try {\n        try (FileOutputStream os = new FileOutputStream(file)) {\n          os.write(1);\n        }\n        diskCache.commitPut(key, file);\n      } finally {\n        diskCache.abortPutIfNotCommitted(key, file);\n      }\n\n      assertThat(diskCache.get(key)).isNotNull();\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/test/java/com/bumptech/glide/integration/sqljournaldiskcache/DiskCacheUtils.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport org.junit.rules.ExternalResource;\n\nfinal class DiskCacheUtils {\n\n  private DiskCacheUtils() {}\n\n  static void writeToFile(File file, String data) {\n    byte[] bytes = data.getBytes();\n    writeToFile(file, bytes);\n  }\n\n  static void writeToFile(File file, byte[] bytes) {\n    try (OutputStream os = new FileOutputStream(file)) {\n      os.write(bytes);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  static byte[] readFromFile(File file) {\n    byte[] result = new byte[(int) file.length()];\n\n    try (FileInputStream is = new FileInputStream(file)) {\n      int readSoFar = 0;\n      int read;\n      while ((read = is.read(result, readSoFar, result.length - readSoFar)) != -1\n          && readSoFar < result.length) {\n        readSoFar += read;\n      }\n      if (readSoFar != result.length) {\n        throw new IllegalStateException(\"Failed to read all data from: \" + file);\n      }\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n    return result;\n  }\n\n  private static void deleteRecursively(File file) {\n    if (file.isDirectory()) {\n      File[] children = file.listFiles();\n      if (children != null) {\n        for (File f : children) {\n          deleteRecursively(f);\n        }\n      }\n    } else {\n      if (!file.delete() && file.exists()) {\n        throw new IllegalStateException(\"Failed to delete; \" + file);\n      }\n    }\n  }\n\n  static final class DiskCacheDirRule extends ExternalResource {\n\n    private File cacheDir;\n\n    @Override\n    protected void before() throws Throwable {\n      cacheDir =\n          new File(ApplicationProvider.getApplicationContext().getCacheDir(), \"test_sql_cache\");\n      super.before();\n    }\n\n    @Override\n    protected void after() {\n      super.after();\n      deleteRecursively(cacheDir);\n    }\n\n    void cleanup() {\n      deleteRecursively(cacheDir);\n    }\n\n    File diskCacheDir() {\n      return cacheDir;\n    }\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/test/java/com/bumptech/glide/integration/sqljournaldiskcache/JournaledLruDiskCacheTest.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.when;\nimport static org.robolectric.Shadows.shadowOf;\n\nimport android.content.Context;\nimport android.os.Looper;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.integration.sqljournaldiskcache.DiskCacheUtils.DiskCacheDirRule;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.File;\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.Collections;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class JournaledLruDiskCacheTest {\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  @Rule public final DiskCacheDirRule diskCacheDirRule = new DiskCacheDirRule();\n\n  private final TestClock testClock = new TestClock();\n  private JournaledLruDiskCache cache;\n  private int size;\n  private FileSystem fileSystem;\n  private DiskCacheDbHelper dbHelper;\n  private File cacheDir;\n\n  @Before\n  public void setUp() {\n    dbHelper = DiskCacheDbHelper.forTesting(context);\n\n    cacheDir = diskCacheDirRule.diskCacheDir();\n    fileSystem = spy(new FileSystem() {});\n    size = 1024;\n    cache = newCache();\n  }\n\n  private JournaledLruDiskCache newCache() {\n    return newCache(/* evictionSlopMultiplier= */ 0f);\n  }\n\n  private JournaledLruDiskCache newCache(float evictionSlopMultiplier) {\n    return new JournaledLruDiskCache(\n        cacheDir,\n        dbHelper,\n        fileSystem,\n        size,\n        Looper.getMainLooper(),\n        evictionSlopMultiplier,\n        /* updateModifiedTimeBatchSize= */ 1,\n        /* staleEvictionThresholdMs= */ Long.MAX_VALUE,\n        testClock::currentTimeMillis);\n  }\n\n  @After\n  public void tearDown() {\n    dbHelper.close();\n  }\n\n  @Test\n  public void beginPut_createsCanaryFile() {\n    cache.beginPut(\"key\");\n    assertThat(cacheDir.listFiles()).hasLength(1);\n  }\n\n  @Test\n  public void beginPut_withExistingFileForKey_returnsNull() {\n    String key = \"key\";\n    File file = cache.beginPut(key);\n    try {\n      DiskCacheUtils.writeToFile(file, \"data\");\n      cache.commitPut(key, file);\n    } finally {\n      cache.abortPutIfNotCommitted(key, file);\n    }\n    File secondPutFile = cache.beginPut(key);\n    assertThat(secondPutFile).isNull();\n  }\n\n  @Test\n  public void commitPut_withFailedPreviousWrite_leavesSizeConsistent() {\n    String key = \"key\";\n\n    File temp = cache.beginPut(key);\n    try {\n      when(fileSystem.rename(temp, new File(cacheDir, key))).thenReturn(false).thenCallRealMethod();\n      // Write a file so large it should get evicted immediately\n      byte[] bytes = new byte[size * 2];\n      DiskCacheUtils.writeToFile(temp, bytes);\n      cache.commitPut(key, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(key, temp);\n    }\n\n    // Verify file was evicted and size is 0 since the big file was evicted.\n    assertThat(getSize(cacheDir)).isEqualTo(0);\n    assertThat(cache.getCurrentSizeBytes()).isEqualTo(0);\n  }\n\n  @Test\n  public void commitPut_withFailedPreviousWrite_replacesContent() {\n    String key = \"key\";\n\n    File temp = cache.beginPut(key);\n    // This is a spy, rename below actually performs the rename (which just renames nothing to\n    // nothing in this case), it must come before writeToFile or commitPut.\n    when(fileSystem.rename(temp, new File(cacheDir, key))).thenReturn(false).thenCallRealMethod();\n    DiskCacheUtils.writeToFile(temp, \"first data\");\n    cache.commitPut(key, temp);\n\n    // If the app crashes prior to abortIfNotCommitted:\n    cache = newCache();\n\n    String expectedData = \"second data\";\n    temp = cache.beginPut(key);\n    try {\n      DiskCacheUtils.writeToFile(temp, expectedData);\n      cache.commitPut(key, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(key, temp);\n    }\n\n    assertThat(cache.get(key)).isNotNull();\n    assertThat(readFromFile(cache.get(key))).isEqualTo(expectedData);\n  }\n\n  @Test\n  public void testAbortPutIfNotCommitted_handlesNullFiles() {\n    String key = \"key\";\n    cache.beginPut(key);\n    cache.abortPutIfNotCommitted(key, null);\n  }\n\n  @Test\n  public void abortPutIfNotCommitted_decrementsSizeIfRenameToFails() {\n    // Write a large File and then fail to rename it so the journal size temporarily doesn't match\n    // the file system size. The slop multiplier will then cause the cache to calculate an amount\n    // to delete that is more than the number of Files available, unless we've properly accounted\n    // for the rename failure.\n    cache = newCache(/* evictionSlopMultiplier= */ 0.5f);\n\n    String largeKey = \"large\";\n    File file = cache.beginPut(largeKey);\n    try {\n      // This is a spy, rename below actually performs the rename (which just renames nothing to\n      // nothing in this case), it must come before writeToFile or commitPut.\n      when(fileSystem.rename(file, new File(cacheDir, largeKey))).thenReturn(false);\n      byte[] bytes = new byte[size - 1];\n      DiskCacheUtils.writeToFile(file, bytes);\n      cache.commitPut(largeKey, file);\n    } finally {\n      cache.abortPutIfNotCommitted(largeKey, file);\n    }\n\n    String smallKey = \"key\";\n    int totalSmallFiles = 2;\n    for (int i = 0; i < totalSmallFiles; i++) {\n      String key = smallKey + i;\n      File smallFile = cache.beginPut(key);\n      try {\n        byte[] bytes = new byte[(size / totalSmallFiles) - 1];\n        DiskCacheUtils.writeToFile(smallFile, bytes);\n        cache.commitPut(key, smallFile);\n      } finally {\n        cache.abortPutIfNotCommitted(key, smallFile);\n      }\n    }\n\n    for (int i = 0; i < totalSmallFiles; i++) {\n      assertThat(cache.get(smallKey + i)).isNotNull();\n    }\n  }\n\n  @Test\n  public void abortPutIfNotCommitted_decrementsSizeInJournalIfRenameToFails() {\n    cache = newCache(/* evictionSlopMultiplier= */ 0.5f);\n\n    String largeKey = \"large\";\n    File file = cache.beginPut(largeKey);\n    try {\n      // This is a spy, rename below actually performs the rename (which just renames nothing to\n      // nothing in this case), it must come before writeToFile or commitPut.\n      when(fileSystem.rename(file, new File(cacheDir, largeKey))).thenReturn(false);\n      byte[] bytes = new byte[size - 1];\n      DiskCacheUtils.writeToFile(file, bytes);\n      cache.commitPut(largeKey, file);\n    } finally {\n      cache.abortPutIfNotCommitted(largeKey, file);\n    }\n\n    // Re-open the cache.\n    cache = newCache(/* evictionSlopMultiplier= */ 0.5f);\n\n    String smallKey = \"key\";\n    int totalSmallFiles = 2;\n    for (int i = 0; i < totalSmallFiles; i++) {\n      String key = smallKey + i;\n      File smallFile = cache.beginPut(key);\n      try {\n        byte[] bytes = new byte[(size / totalSmallFiles) - 1];\n        DiskCacheUtils.writeToFile(smallFile, bytes);\n        cache.commitPut(key, smallFile);\n      } finally {\n        cache.abortPutIfNotCommitted(key, smallFile);\n      }\n    }\n\n    for (int i = 0; i < totalSmallFiles; i++) {\n      assertThat(cache.get(smallKey + i)).isNotNull();\n    }\n  }\n\n  @Test(expected = IllegalMonitorStateException.class)\n  public void testAbortPutIfNotCommitted_withoutBeginPut_throws() {\n    cache.abortPutIfNotCommitted(\"fakeKey\", new File(cacheDir, \"fakeFile\"));\n  }\n\n  @Test\n  public void get_afterCommittedPut_returnsFileWithData() {\n    String key = \"myKey\";\n    String data = \"data\";\n\n    File toPut = cache.beginPut(key);\n    try {\n      DiskCacheUtils.writeToFile(toPut, data);\n      cache.commitPut(key, toPut);\n    } finally {\n      cache.abortPutIfNotCommitted(key, toPut);\n    }\n\n    File fromGet = cache.get(key);\n\n    assertThat(readFromFile(fromGet)).isEqualTo(data);\n  }\n\n  @Test\n  public void get_beforePut_returnsNull() {\n    assertThat(cache.get(\"key\")).isNull();\n  }\n\n  @Test\n  public void get_afterAbortedPut_returnsNull() {\n    String key = \"key\";\n    File toPut = cache.beginPut(key);\n    try {\n      String data = \"data\";\n      DiskCacheUtils.writeToFile(toPut, data);\n    } finally {\n      cache.abortPutIfNotCommitted(key, toPut);\n    }\n\n    File fromGet = cache.get(key);\n    assertThat(fromGet).isNull();\n  }\n\n  @Test\n  public void abortPutIfNotCommitted_whenNotCommitted_discardsData() {\n    String key = \"key\";\n    File toPut = cache.beginPut(key);\n    try {\n      String data = \"data\";\n      DiskCacheUtils.writeToFile(toPut, data);\n    } finally {\n      cache.abortPutIfNotCommitted(key, toPut);\n    }\n\n    assertThat(cacheDir.listFiles()).hasLength(1);\n    assertThat(getSize(cacheDir)).isEqualTo(0L);\n  }\n\n  @Test\n  public void commitPut_runsEvictionIfNecessary() {\n    int totalFiles = 5;\n    byte[] data = new byte[size / 3];\n    for (int i = 0; i < totalFiles; i++) {\n      String key = \"key\" + i;\n      File file = cache.beginPut(key);\n      try {\n        DiskCacheUtils.writeToFile(file, data);\n        cache.commitPut(key, file);\n      } finally {\n        cache.abortPutIfNotCommitted(key, file);\n      }\n    }\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isLessThan((long) size);\n  }\n\n  @Test\n  public void eviction_removesFirstPutFile() {\n    int totalFiles = 3;\n    byte[] data = new byte[(size / totalFiles) + 1];\n    String keyBase = \"key\";\n    for (int i = 0; i < totalFiles; i++) {\n      String key = keyBase + i;\n      File file = cache.beginPut(key);\n      try {\n        DiskCacheUtils.writeToFile(file, data);\n        cache.commitPut(key, file);\n      } finally {\n        cache.abortPutIfNotCommitted(key, file);\n      }\n      testClock.advance(Duration.ofMillis(1));\n    }\n\n    onIdleWorkerThread();\n\n    assertThat(cache.get(keyBase + 0)).isNull();\n    assertThat(cache.get(keyBase + 1)).isNotNull();\n    assertThat(cache.get(keyBase + 2)).isNotNull();\n  }\n\n  // Eviction is triggered by posts.\n  private static void onIdleWorkerThread() {\n    shadowOf(Looper.getMainLooper()).idle();\n  }\n\n  @Test\n  public void eviction_withGets_removesLeastRecentlyUsedFile() {\n    int totalFiles = 3;\n    byte[] data = new byte[(size / totalFiles) + 1];\n    String keyBase = \"key\";\n    for (int i = 0; i < totalFiles; i++) {\n      String key = keyBase + i;\n      File file = cache.beginPut(key);\n      try {\n        DiskCacheUtils.writeToFile(file, data);\n        cache.commitPut(key, file);\n      } finally {\n        cache.abortPutIfNotCommitted(key, file);\n      }\n\n      if (i == 1) {\n        testClock.advance(Duration.ofMillis(1));\n        cache.get(keyBase + 0);\n      }\n      testClock.advance(Duration.ofMillis(1));\n    }\n\n    onIdleWorkerThread();\n\n    assertThat(cache.get(keyBase + 0)).isNotNull();\n    assertThat(cache.get(keyBase + 1)).isNull();\n    assertThat(cache.get(keyBase + 2)).isNotNull();\n  }\n\n  @Test\n  public void eviction_withManyEntries_updatesSizeCorrectly() {\n    int numSmallFiles = 3;\n    byte[] largeData = new byte[size - 1];\n    byte[] smallData = new byte[(size / numSmallFiles) - 1];\n    String largeKey = \"largeKey\";\n\n    for (int i = 0; i < 2; i++) {\n      String key = largeKey + i;\n      File largeFile = cache.beginPut(key);\n      try {\n        DiskCacheUtils.writeToFile(largeFile, largeData);\n        cache.commitPut(key, largeFile);\n      } finally {\n        cache.abortPutIfNotCommitted(key, largeFile);\n      }\n      testClock.advance(Duration.ofMillis(1));\n    }\n\n    String smallkey = \"smallKey\";\n    for (int i = 0; i < numSmallFiles; i++) {\n      String key = smallkey + i;\n      File file = cache.beginPut(key);\n      try {\n        DiskCacheUtils.writeToFile(file, smallData);\n        cache.commitPut(key, file);\n      } finally {\n        cache.abortPutIfNotCommitted(key, file);\n      }\n      testClock.advance(Duration.ofMillis(1));\n    }\n\n    onIdleWorkerThread();\n\n    for (int i = 0; i < numSmallFiles; i++) {\n      assertThat(cache.get(smallkey + i)).isNotNull();\n    }\n  }\n\n  // The goal here is to ensure our sql batching works as expected. We aim for more than 999 files\n  // because sql only allows 999 arguments for a single query.\n  @Test\n  public void eviction_writeManyFiles_evictsManyEntries() throws IOException {\n    String smallKey = \"small\";\n    for (int i = 0; i < 1000; i++) {\n      String key = smallKey + i;\n      File file = cache.beginPut(key);\n      try {\n        if (!file.createNewFile()) {\n          throw new IllegalStateException(\"Failed to create: \" + file);\n        }\n        cache.commitPut(key, file);\n      } finally {\n        cache.abortPutIfNotCommitted(key, file);\n      }\n      testClock.advance(Duration.ofMillis(1));\n    }\n\n    String largeKey = \"large\";\n    File largeFile = cache.beginPut(largeKey);\n    try {\n      byte[] bytes = new byte[size + 1];\n      DiskCacheUtils.writeToFile(largeFile, bytes);\n      cache.commitPut(largeKey, largeFile);\n    } finally {\n      cache.abortPutIfNotCommitted(largeKey, largeFile);\n    }\n\n    onIdleWorkerThread();\n\n    assertThat(cacheDir.listFiles()).hasLength(1);\n  }\n\n  @Test\n  public void delete_missingFile_ignored() {\n    cache.delete(\"fakeKey\");\n  }\n\n  @Test\n  public void delete_removesEntryForKey() {\n    String key = \"key\";\n    File temp = cache.beginPut(key);\n    try {\n      DiskCacheUtils.writeToFile(temp, \"data\");\n      cache.commitPut(key, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(key, temp);\n    }\n\n    assertThat(cache.get(key)).isNotNull();\n    assertThat(cacheDir.listFiles()).hasLength(2);\n\n    cache.delete(key);\n\n    assertThat(cache.get(key)).isNull();\n    assertThat(cacheDir.listFiles()).hasLength(1);\n  }\n\n  @Test\n  public void delete_withInProgressWriteForKey_doesNotDeleteKey() {\n    String key = \"key\";\n    File file = new File(cacheDir, key);\n    when(fileSystem.delete(file)).thenReturn(false);\n    when(fileSystem.exists(file)).thenReturn(false).thenReturn(true);\n\n    assertThat(cache.delete(Collections.singletonList(key))).isEmpty();\n  }\n\n  @Test\n  public void delete_onPreviouslyFailedKey_doesNotDecrementCacheSizeTwice() {\n    String key = \"key\";\n    File file = new File(cacheDir, key);\n    // first delete attempt, second delete attempt.\n    when(fileSystem.delete(file)).thenReturn(false).thenCallRealMethod();\n\n    File temp = cache.beginPut(key);\n    try {\n      byte[] bytes = new byte[size - 1];\n      DiskCacheUtils.writeToFile(temp, bytes);\n      cache.commitPut(key, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(key, temp);\n    }\n\n    cache.delete(key);\n    cache.delete(key);\n\n    // We should have successfully deleted the file.\n    assertThat(cacheDir.listFiles()).hasLength(1);\n\n    String otherKey = \"other\";\n    for (int i = 0; i < 2; i++) {\n      String currentKey = otherKey + i;\n      temp = cache.beginPut(currentKey);\n      try {\n        byte[] bytes = new byte[size - 1];\n        DiskCacheUtils.writeToFile(temp, bytes);\n        cache.commitPut(currentKey, temp);\n      } finally {\n        cache.abortPutIfNotCommitted(currentKey, file);\n      }\n    }\n\n    onIdleWorkerThread();\n    // Only one File should remain. Two will if we double counted the delete of the single key.\n    assertThat(cacheDir.listFiles()).hasLength(2);\n  }\n\n  @Test\n  public void clear_removesAllEntriesAndFiles() {\n    String firstKey = \"key1\";\n    File temp = cache.beginPut(firstKey);\n    try {\n      DiskCacheUtils.writeToFile(temp, \"data1\");\n      cache.commitPut(firstKey, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(firstKey, temp);\n    }\n    testClock.advance(Duration.ofMillis(1));\n\n    String secondKey = \"key2\";\n    temp = cache.beginPut(secondKey);\n    try {\n      DiskCacheUtils.writeToFile(temp, secondKey);\n      cache.commitPut(secondKey, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(secondKey, temp);\n    }\n\n    assertThat(cache.get(firstKey)).isNotNull();\n    assertThat(cache.get(secondKey)).isNotNull();\n    assertThat(cacheDir.listFiles()).hasLength(3);\n\n    cache.clear();\n\n    assertThat(cache.get(firstKey)).isNull();\n    assertThat(cache.get(secondKey)).isNull();\n    // Now it should just contain the canary.\n    assertThat(cacheDir.listFiles()).hasLength(1);\n  }\n\n  @Test\n  public void recovery_withPartialWriteAndJournalEntry_deletesTempFileAndDecrementsSize() {\n    String successKey = \"success\";\n    File successTemp = cache.beginPut(successKey);\n    try {\n      byte[] bytes = new byte[size / 2];\n      DiskCacheUtils.writeToFile(successTemp, bytes);\n      cache.commitPut(successKey, successTemp);\n    } finally {\n      cache.abortPutIfNotCommitted(successKey, successTemp);\n    }\n    onIdleWorkerThread();\n    Preconditions.checkNotNull(cache.get(successKey));\n\n    String failKey = \"fail\";\n    File failPermanent = new File(cacheDir, failKey);\n    File failTemp = cache.beginPut(failKey);\n    when(fileSystem.rename(failTemp, failPermanent)).thenReturn(false).thenCallRealMethod();\n\n    // Simulate a crash by failing to calll abortPutIfNotCommitted.\n    byte[] bytes1 = new byte[(size / 2) - 1];\n    DiskCacheUtils.writeToFile(failTemp, bytes1);\n    cache.commitPut(failKey, failTemp);\n\n    // We should have the success permanent file, the failed temp file, and the canary file.\n    assertThat(cacheDir.listFiles()).hasLength(3);\n\n    // Re-open the cache.\n    cache = newCache();\n\n    String secondSuccessKey = \"secondSuccess\";\n    File secondSuccessTemp = cache.beginPut(secondSuccessKey);\n    try {\n      byte[] bytes = new byte[(size / 2) - 1];\n      DiskCacheUtils.writeToFile(secondSuccessTemp, bytes);\n      cache.commitPut(secondSuccessKey, secondSuccessTemp);\n    } finally {\n      cache.abortPutIfNotCommitted(secondSuccessKey, secondSuccessTemp);\n    }\n\n    onIdleWorkerThread();\n    assertThat(cache.get(successKey)).isNotNull();\n    assertThat(cache.get(failKey)).isNull();\n    assertThat(cache.get(secondSuccessKey)).isNotNull();\n  }\n\n  @Test\n  public void recovery_withPartialWriteAndNoJournalEntry_deletesTempFile() {\n    String partialWriteKey = \"partialWriteKey\";\n    File partialWriteTemp = cache.beginPut(partialWriteKey);\n    byte[] bytes1 = new byte[size];\n    DiskCacheUtils.writeToFile(partialWriteTemp, bytes1);\n\n    cache = newCache();\n\n    // Verify we haven't done unexpected things to the cache size.\n    String baseKey = \"key\";\n    for (int i = 0; i < 4; i++) {\n      String key = baseKey + i;\n      File temp = cache.beginPut(key);\n      try {\n        byte[] bytes = new byte[(size / 4) + 1];\n        DiskCacheUtils.writeToFile(temp, bytes);\n        cache.commitPut(key, temp);\n      } finally {\n        cache.abortPutIfNotCommitted(key, temp);\n      }\n      testClock.advance(Duration.ofMillis(1));\n    }\n\n    onIdleWorkerThread();\n\n    // Canary + 3 smaller files.\n    assertThat(cacheDir.listFiles()).hasLength(4);\n\n    for (int i = 0; i < 4; i++) {\n      String key = baseKey + i;\n      if (i == 0) {\n        assertThat(cache.get(key)).isNull();\n      } else {\n        assertThat(cache.get(key)).isNotNull();\n      }\n    }\n  }\n\n  @Test\n  public void recovery_withPendingDeleteForFile_deletesFileAndEntry() {\n    String key = \"key\";\n    File permanentFile = new File(cacheDir, key);\n    when(fileSystem.delete(permanentFile)).thenReturn(false).thenCallRealMethod();\n\n    File temp = cache.beginPut(key);\n    try {\n      DiskCacheUtils.writeToFile(temp, \"data\");\n      cache.commitPut(key, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(key, temp);\n    }\n\n    // Failed delete.\n    cache.delete(key);\n\n    // Failed delete + canary.\n    assertThat(cacheDir.listFiles()).hasLength(2);\n\n    cache = newCache();\n\n    String otherKey = \"other\";\n    temp = cache.beginPut(otherKey);\n    try {\n      DiskCacheUtils.writeToFile(temp, \"otherData\");\n      cache.commitPut(otherKey, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(otherKey, temp);\n    }\n\n    onIdleWorkerThread();\n    assertThat(cache.get(key)).isNull();\n    // Canary + second key.\n    assertThat(cacheDir.listFiles()).hasLength(2);\n  }\n\n  @Test\n  public void recovery_withInProgressWrite_doesNotDeleteFile() {\n    String key = \"key\";\n    String data = \"data\";\n    File temp = cache.beginPut(key);\n    try {\n      DiskCacheUtils.writeToFile(temp, data);\n      cache.commitPut(key, temp);\n    } finally {\n      cache.abortPutIfNotCommitted(key, temp);\n    }\n    // Simulate a concurrent recovery attempt now obtaining the write lock.\n    cache.recoverPartialWrite(temp);\n    // Make sure that it doesn't delete the fully written file\n    File cacheFile = cache.get(key);\n    assertThat(cacheFile).isNotNull();\n    assertThat(readFromFile(cacheFile)).isEqualTo(data);\n  }\n\n  @Test\n  public void setMaximumSizeBytes_increaseCacheSize_doesNotEvictEntries() {\n    String key = \"key\";\n    File toPut = cache.beginPut(key);\n\n    cache.setMaximumSizeBytes(size * 3);\n    try {\n      // write a file that exceeds the old maximum\n      byte[] bytes = new byte[size * 3];\n      DiskCacheUtils.writeToFile(toPut, bytes);\n      cache.commitPut(key, toPut);\n    } finally {\n      cache.abortPutIfNotCommitted(key, toPut);\n    }\n\n    assertThat(getSize(cacheDir)).isEqualTo(size * 3);\n    assertThat(cache.getCurrentSizeBytes()).isEqualTo(size * 3);\n  }\n\n  @Test\n  public void setMaximumSizeBytes_increaseCacheSize_evictEntries() {\n    String key = \"key\";\n    File toPut = cache.beginPut(key);\n\n    cache.setMaximumSizeBytes(size * 2);\n    try {\n      // write a file that exceeds the new max\n      byte[] bytes = new byte[size * 3];\n      DiskCacheUtils.writeToFile(toPut, bytes);\n      cache.commitPut(key, toPut);\n    } finally {\n      cache.abortPutIfNotCommitted(key, toPut);\n    }\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isEqualTo(0);\n    assertThat(cache.getCurrentSizeBytes()).isEqualTo(0);\n  }\n\n  @Test\n  public void setMaximumSizeBytes_decreaseCacheSize_doesNotEvictEntries() {\n    String key = \"key\";\n    File toPut = cache.beginPut(key);\n    int tinySize = 20;\n    try {\n      // write a file that satisfies original and new cache space\n      byte[] bytes = new byte[tinySize];\n      DiskCacheUtils.writeToFile(toPut, bytes);\n      cache.commitPut(key, toPut);\n    } finally {\n      cache.abortPutIfNotCommitted(key, toPut);\n    }\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isEqualTo(tinySize);\n    assertThat(cache.getCurrentSizeBytes()).isEqualTo(tinySize);\n\n    // shrinking size should not evict\n    int newMax = size - 100;\n    assertThat(newMax).isLessThan(size);\n    cache.setMaximumSizeBytes(newMax);\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isAtMost(tinySize);\n    assertThat(cache.getCurrentSizeBytes()).isAtMost(tinySize);\n  }\n\n  @Test\n  public void setMaximumSizeBytes_decreaseCacheSize_evictEntries() {\n    String key = \"key\";\n    File toPut = cache.beginPut(key);\n\n    try {\n      // write a file that satisfies original cache space\n      byte[] bytes = new byte[size];\n      DiskCacheUtils.writeToFile(toPut, bytes);\n      cache.commitPut(key, toPut);\n    } finally {\n      cache.abortPutIfNotCommitted(key, toPut);\n    }\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isEqualTo(size);\n    assertThat(cache.getCurrentSizeBytes()).isEqualTo(size);\n\n    // shrinking size should evict cache as needed\n    int newMax = size - 100;\n    assertThat(newMax).isLessThan(size);\n    cache.setMaximumSizeBytes(newMax);\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isAtMost(newMax);\n    assertThat(cache.getCurrentSizeBytes()).isAtMost(newMax);\n  }\n\n  @Test\n  public void setMaximumSizeBytes_decreaseCacheSize_evictStaleEntries() {\n    String keyStale = \"keyStale\";\n    String keyLru = \"keyLru\";\n    File toPutStale = cache.beginPut(keyStale);\n    File toPutLru = cache.beginPut(keyLru);\n    int smallSizeBytes = 1;\n\n    try {\n      byte[] bytes = new byte[size - smallSizeBytes];\n      DiskCacheUtils.writeToFile(toPutStale, bytes);\n      cache.commitPut(keyStale, toPutStale);\n    } finally {\n      cache.abortPutIfNotCommitted(keyStale, toPutStale);\n    }\n\n    // make the next entry far ahead in the future\n    testClock.set(90);\n\n    try {\n      byte[] bytes = new byte[smallSizeBytes];\n      DiskCacheUtils.writeToFile(toPutLru, bytes);\n      cache.commitPut(keyLru, toPutLru);\n    } finally {\n      cache.abortPutIfNotCommitted(keyLru, toPutLru);\n    }\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isEqualTo(size);\n    assertThat(cache.getCurrentSizeBytes()).isEqualTo(size);\n\n    // shrinking size should evict cache as needed\n    int newMax = size - 100;\n    assertThat(newMax).isLessThan(size);\n    assertThat(smallSizeBytes).isLessThan(newMax);\n    cache.setMaximumSizeBytes(newMax);\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isAtMost(smallSizeBytes);\n    assertThat(cache.getCurrentSizeBytes()).isAtMost(smallSizeBytes);\n  }\n\n  @Test\n  public void setMaximumSizeBytes_decreaseCacheSize_evictLruEntries() {\n    String keyStale = \"keyStale\";\n    String keyLru = \"keyLru\";\n    File toPutStale = cache.beginPut(keyStale);\n    File toPutLru = cache.beginPut(keyLru);\n    int smallSizeBytes = 1;\n\n    try {\n      byte[] bytes = new byte[smallSizeBytes];\n      DiskCacheUtils.writeToFile(toPutStale, bytes);\n      cache.commitPut(keyStale, toPutStale);\n    } finally {\n      cache.abortPutIfNotCommitted(keyStale, toPutStale);\n    }\n\n    // make the next entry far ahead in the future\n    testClock.set(90);\n\n    try {\n      byte[] bytes = new byte[size - smallSizeBytes];\n      DiskCacheUtils.writeToFile(toPutLru, bytes);\n      cache.commitPut(keyLru, toPutLru);\n    } finally {\n      cache.abortPutIfNotCommitted(keyLru, toPutLru);\n    }\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isEqualTo(size);\n    assertThat(cache.getCurrentSizeBytes()).isEqualTo(size);\n\n    // shrinking size should evict cache as needed\n    int newMax = size - 100;\n    assertThat(newMax).isLessThan(size);\n    assertThat(smallSizeBytes).isLessThan(newMax);\n    cache.setMaximumSizeBytes(newMax);\n\n    onIdleWorkerThread();\n    assertThat(getSize(cacheDir)).isAtMost(smallSizeBytes);\n    assertThat(cache.getCurrentSizeBytes()).isAtMost(smallSizeBytes);\n  }\n\n  private static long getSize(File file) {\n    long result = 0;\n    if (file.isDirectory()) {\n      for (File f : file.listFiles()) {\n        result += getSize(f);\n      }\n    } else {\n      result = file.length();\n    }\n    return result;\n  }\n\n  private static String readFromFile(File file) {\n    byte[] data = DiskCacheUtils.readFromFile(file);\n    return new String(data);\n  }\n}\n"
  },
  {
    "path": "integration/sqljournaldiskcache/src/test/java/com/bumptech/glide/integration/sqljournaldiskcache/TestClock.java",
    "content": "package com.bumptech.glide.integration.sqljournaldiskcache;\n\nimport java.time.Duration;\n\nfinal class TestClock implements Clock {\n  private long currentTimeMillis = 0L;\n\n  @Override\n  public long currentTimeMillis() {\n    return currentTimeMillis;\n  }\n\n  void set(long timeMillis) {\n    currentTimeMillis = timeMillis;\n  }\n\n  void advance(Duration duration) {\n    currentTimeMillis += duration.toMillis();\n  }\n}\n"
  },
  {
    "path": "integration/volley/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.integration.volley\"\n\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    api(libs.volley)\n    api(libs.androidx.annotation)\n\n    annotationProcessor(project(\":annotation:compiler\"))\n\n    testImplementation(project(\":testutil\"))\n    testImplementation(libs.truth)\n    testImplementation(libs.junit)\n    testImplementation(libs.mockito.core)\n    testImplementation(libs.robolectric)\n    testImplementation(libs.mockwebserver)\n    testImplementation(libs.androidx.test.core)\n    testImplementation(libs.androidx.junit)\n    testImplementation(libs.androidx.test.runner)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "integration/volley/gradle.properties",
    "content": "POM_NAME=Glide Volley Integration\nPOM_ARTIFACT_ID=volley-integration\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=An integration library to use Volley to fetch data over http/https in Glide\n"
  },
  {
    "path": "integration/volley/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n</lint>\n"
  },
  {
    "path": "integration/volley/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application>\n        <meta-data\n            android:name=\"com.bumptech.glide.integration.volley.VolleyGlideModule\"\n            android:value=\"GlideModule\"/>\n    </application>\n</manifest>\n"
  },
  {
    "path": "integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyGlideModule.java",
    "content": "package com.bumptech.glide.integration.volley;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport java.io.InputStream;\n\n/**\n * A {@link com.bumptech.glide.module.GlideModule} implementation to replace Glide's default {@link\n * java.net.HttpURLConnection} based {@link com.bumptech.glide.load.model.ModelLoader} with a Volley\n * based {@link com.bumptech.glide.load.model.ModelLoader}.\n *\n * <p>If you're using gradle, you can include this module simply by depending on the aar, the module\n * will be merged in by manifest merger. For other build systems or for more more information, see\n * {@link com.bumptech.glide.module.GlideModule}.\n *\n * @deprecated Replaced with {@link VolleyLibraryGlideModule}.\n */\n@Deprecated\n@SuppressWarnings(\"deprecation\")\npublic class VolleyGlideModule implements com.bumptech.glide.module.GlideModule {\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    // Do nothing.\n  }\n\n  @Override\n  public void registerComponents(Context context, Glide glide, Registry registry) {\n    registry.replace(GlideUrl.class, InputStream.class, new VolleyUrlLoader.Factory(context));\n  }\n}\n"
  },
  {
    "path": "integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyLibraryGlideModule.java",
    "content": "package com.bumptech.glide.integration.volley;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.module.LibraryGlideModule;\nimport java.io.InputStream;\n\n/**\n * A {@link com.bumptech.glide.module.GlideModule} implementation to replace Glide's default {@link\n * java.net.HttpURLConnection} based {@link com.bumptech.glide.load.model.ModelLoader} with a Volley\n * based {@link com.bumptech.glide.load.model.ModelLoader}.\n *\n * <p>For Applications that depend on this library and include an {@link AppGlideModule} and Glide's\n * annotation processor, this class will be automatically included.\n */\n@GlideModule\npublic class VolleyLibraryGlideModule extends LibraryGlideModule {\n  @Override\n  public void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    registry.replace(GlideUrl.class, InputStream.class, new VolleyUrlLoader.Factory(context));\n  }\n}\n"
  },
  {
    "path": "integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyRequestFactory.java",
    "content": "package com.bumptech.glide.integration.volley;\n\nimport com.android.volley.Request;\nimport com.android.volley.Request.Priority;\nimport com.bumptech.glide.load.data.DataFetcher.DataCallback;\nimport java.io.InputStream;\nimport java.util.Map;\n\n/** Used to construct a custom Volley request, such as for authentication header decoration. */\npublic interface VolleyRequestFactory {\n\n  /**\n   * Returns a Volley request for the given image url. The given future should be put as a listener\n   * or called when the request completes.\n   */\n  Request<byte[]> create(\n      String url,\n      DataCallback<? super InputStream> callback,\n      Priority priority,\n      Map<String, String> headers);\n}\n"
  },
  {
    "path": "integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyStreamFetcher.java",
    "content": "package com.bumptech.glide.integration.volley;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.util.Collections;\nimport java.util.Map;\n\n/** A DataFetcher backed by volley for fetching images via http. */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic class VolleyStreamFetcher implements DataFetcher<InputStream> {\n  private static final String TAG = \"VolleyStreamFetcher\";\n  public static final VolleyRequestFactory DEFAULT_REQUEST_FACTORY =\n      new VolleyRequestFactory() {\n        @Override\n        public Request<byte[]> create(\n            String url,\n            DataCallback<? super InputStream> callback,\n            Request.Priority priority,\n            Map<String, String> headers) {\n          return new GlideRequest(url, callback, priority, headers);\n        }\n      };\n\n  private final RequestQueue requestQueue;\n  private final VolleyRequestFactory requestFactory;\n  private final GlideUrl url;\n  private volatile Request<byte[]> request;\n\n  @SuppressWarnings(\"unused\")\n  public VolleyStreamFetcher(RequestQueue requestQueue, GlideUrl url) {\n    this(requestQueue, url, DEFAULT_REQUEST_FACTORY);\n  }\n\n  public VolleyStreamFetcher(\n      RequestQueue requestQueue, GlideUrl url, VolleyRequestFactory requestFactory) {\n    this.requestQueue = requestQueue;\n    this.url = url;\n    this.requestFactory = requestFactory;\n  }\n\n  @Override\n  public void loadData(\n      @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {\n    request =\n        requestFactory.create(\n            url.toStringUrl(), callback, glideToVolleyPriority(priority), url.getHeaders());\n    requestQueue.add(request);\n  }\n\n  @Override\n  public void cleanup() {\n    // Do nothing.\n  }\n\n  @Override\n  public void cancel() {\n    Request<byte[]> local = request;\n    if (local != null) {\n      local.cancel();\n    }\n  }\n\n  @NonNull\n  @Override\n  public Class<InputStream> getDataClass() {\n    return InputStream.class;\n  }\n\n  @NonNull\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.REMOTE;\n  }\n\n  private static Request.Priority glideToVolleyPriority(@NonNull Priority priority) {\n    switch (priority) {\n      case LOW:\n        return Request.Priority.LOW;\n      case HIGH:\n        return Request.Priority.HIGH;\n      case IMMEDIATE:\n        return Request.Priority.IMMEDIATE;\n      default:\n        return Request.Priority.NORMAL;\n    }\n  }\n\n  /**\n   * Default {@link com.android.volley.Request} implementation for Glide that receives errors and\n   * results on volley's background thread.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public static class GlideRequest extends Request<byte[]> {\n    private final DataCallback<? super InputStream> callback;\n    private final Priority priority;\n    private final Map<String, String> headers;\n\n    public GlideRequest(String url, DataCallback<? super InputStream> callback, Priority priority) {\n      this(url, callback, priority, Collections.<String, String>emptyMap());\n    }\n\n    public GlideRequest(\n        String url,\n        DataCallback<? super InputStream> callback,\n        Priority priority,\n        Map<String, String> headers) {\n      super(Method.GET, url, null);\n      this.callback = callback;\n      this.priority = priority;\n      this.headers = headers;\n    }\n\n    @Override\n    public Map<String, String> getHeaders() {\n      return headers;\n    }\n\n    @Override\n    public Priority getPriority() {\n      return priority;\n    }\n\n    @Override\n    protected VolleyError parseNetworkError(VolleyError volleyError) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Volley failed to retrieve response\", volleyError);\n      }\n      if (!isCanceled()) {\n        callback.onLoadFailed(volleyError);\n      }\n      return super.parseNetworkError(volleyError);\n    }\n\n    @Override\n    protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {\n      if (!isCanceled()) {\n        callback.onDataReady(new ByteArrayInputStream(response.data));\n      }\n      return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));\n    }\n\n    @Override\n    protected void deliverResponse(byte[] response) {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "integration/volley/src/main/java/com/bumptech/glide/integration/volley/VolleyUrlLoader.java",
    "content": "package com.bumptech.glide.integration.volley;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.toolbox.Volley;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport java.io.InputStream;\n\n/** A simple model loader for fetching media over http/https using Volley. */\npublic class VolleyUrlLoader implements ModelLoader<GlideUrl, InputStream> {\n\n  private final RequestQueue requestQueue;\n  private final VolleyRequestFactory requestFactory;\n\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public VolleyUrlLoader(RequestQueue requestQueue) {\n    this(requestQueue, VolleyStreamFetcher.DEFAULT_REQUEST_FACTORY);\n  }\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public VolleyUrlLoader(RequestQueue requestQueue, VolleyRequestFactory requestFactory) {\n    this.requestQueue = requestQueue;\n    this.requestFactory = requestFactory;\n  }\n\n  @Override\n  public boolean handles(@NonNull GlideUrl url) {\n    return true;\n  }\n\n  @Override\n  public LoadData<InputStream> buildLoadData(\n      @NonNull GlideUrl url, int width, int height, @NonNull Options options) {\n    return new LoadData<>(url, new VolleyStreamFetcher(requestQueue, url, requestFactory));\n  }\n\n  /** The default factory for {@link VolleyUrlLoader}s. */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {\n    private static volatile RequestQueue internalQueue;\n\n    private final VolleyRequestFactory requestFactory;\n    private final RequestQueue requestQueue;\n\n    /** Constructor for a new Factory that runs requests using a static singleton request queue. */\n    public Factory(Context context) {\n      this(getInternalQueue(context));\n    }\n\n    /** Constructor for a new Factory that runs requests using the given {@link RequestQueue}. */\n    public Factory(RequestQueue requestQueue) {\n      this(requestQueue, VolleyStreamFetcher.DEFAULT_REQUEST_FACTORY);\n    }\n\n    /**\n     * Constructor for a new Factory with a custom Volley request factory that runs requests using\n     * the given {@link RequestQueue}.\n     */\n    public Factory(RequestQueue requestQueue, VolleyRequestFactory requestFactory) {\n      this.requestFactory = requestFactory;\n      this.requestQueue = requestQueue;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory factory) {\n      return new VolleyUrlLoader(requestQueue, requestFactory);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n\n    private static RequestQueue getInternalQueue(Context context) {\n      if (internalQueue == null) {\n        synchronized (Factory.class) {\n          if (internalQueue == null) {\n            internalQueue = Volley.newRequestQueue(context);\n          }\n        }\n      }\n      return internalQueue;\n    }\n  }\n}\n"
  },
  {
    "path": "integration/volley/src/test/java/com/bumptech/glide/integration/volley/VolleyStreamFetcherServerTest.java",
    "content": "package com.bumptech.glide.integration.volley;\n\nimport static com.bumptech.glide.testutil.TestUtil.assertStreamOf;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.os.SystemClock;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.Volley;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.Headers;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport okhttp3.mockwebserver.MockResponse;\nimport okhttp3.mockwebserver.MockWebServer;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\nimport org.robolectric.shadows.ShadowSystemClock;\n\n/**\n * Tests {@link com.bumptech.glide.integration.volley.VolleyStreamFetcher} against server responses.\n */\n@RunWith(RobolectricTestRunner.class)\n@Config(\n    manifest = Config.NONE,\n    sdk = 19,\n    shadows = VolleyStreamFetcherServerTest.FakeSystemClock.class)\npublic class VolleyStreamFetcherServerTest {\n  private static final String DEFAULT_PATH = \"/fakepath\";\n\n  @Mock private DataFetcher.DataCallback<InputStream> callback;\n\n  private MockWebServer mockWebServer;\n  private RequestQueue requestQueue;\n  private ArgumentCaptor<InputStream> streamCaptor;\n  private CountDownLatch waitForResponseLatch;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n\n    waitForResponseLatch = new CountDownLatch(1);\n    doAnswer(new CountDown()).when(callback).onDataReady(any(InputStream.class));\n    doAnswer(new CountDown()).when(callback).onLoadFailed(any(Exception.class));\n    requestQueue = Volley.newRequestQueue(ApplicationProvider.getApplicationContext());\n    mockWebServer = new MockWebServer();\n    mockWebServer.start();\n\n    streamCaptor = ArgumentCaptor.forClass(InputStream.class);\n  }\n\n  @After\n  public void tearDown() throws IOException {\n    mockWebServer.shutdown();\n    requestQueue.stop();\n  }\n\n  @Test\n  public void testReturnsInputStreamOnStatusOk() throws Exception {\n    String expected = \"fakedata\";\n    mockWebServer.enqueue(new MockResponse().setBody(expected).setResponseCode(200));\n    DataFetcher<InputStream> fetcher = getFetcher();\n    fetcher.loadData(Priority.HIGH, callback);\n    waitForResponseLatch.await();\n    verify(callback).onDataReady(streamCaptor.capture());\n    assertStreamOf(expected, streamCaptor.getValue());\n  }\n\n  @Test\n  public void testHandlesRedirect301s() throws Exception {\n    String expected = \"fakedata\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(301)\n            .setHeader(\"Location\", mockWebServer.url(\"/redirect\").toString()));\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));\n    getFetcher().loadData(Priority.LOW, callback);\n    waitForResponseLatch.await();\n    verify(callback).onDataReady(streamCaptor.capture());\n    assertStreamOf(expected, streamCaptor.getValue());\n  }\n\n  @Test\n  public void testHandlesRedirect302s() throws Exception {\n    String expected = \"fakedata\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(302)\n            .setHeader(\"Location\", mockWebServer.url(\"/redirect\").toString()));\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));\n    getFetcher().loadData(Priority.LOW, callback);\n    waitForResponseLatch.await();\n    verify(callback).onDataReady(streamCaptor.capture());\n    assertStreamOf(expected, streamCaptor.getValue());\n  }\n\n  @Test\n  public void testHandlesUpToFiveRedirects() throws Exception {\n    int numRedirects = 4;\n    String expected = \"redirectedData\";\n    String redirectBase = \"/redirect\";\n    for (int i = 0; i < numRedirects; i++) {\n      mockWebServer.enqueue(\n          new MockResponse()\n              .setResponseCode(301)\n              .setHeader(\"Location\", mockWebServer.url(redirectBase + i).toString()));\n    }\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));\n\n    getFetcher().loadData(Priority.NORMAL, callback);\n    waitForResponseLatch.await();\n    verify(callback).onDataReady(streamCaptor.capture());\n    assertStreamOf(expected, streamCaptor.getValue());\n\n    assertThat(mockWebServer.takeRequest().getPath()).contains(DEFAULT_PATH);\n    for (int i = 0; i < numRedirects; i++) {\n      assertThat(mockWebServer.takeRequest().getPath()).contains(redirectBase + i);\n    }\n  }\n\n  @Test\n  public void testCallsLoadFailedIfRedirectLocationIsEmpty() throws Exception {\n    for (int i = 0; i < 2; i++) {\n      mockWebServer.enqueue(new MockResponse().setResponseCode(301));\n    }\n\n    getFetcher().loadData(Priority.NORMAL, callback);\n    waitForResponseLatch.await();\n    verify(callback).onLoadFailed(isA(VolleyError.class));\n  }\n\n  @Test\n  public void testCallsLoadFailedIfStatusCodeIsNegativeOne() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(-1));\n    getFetcher().loadData(Priority.LOW, callback);\n    waitForResponseLatch.await();\n    verify(callback).onLoadFailed(isA(VolleyError.class));\n  }\n\n  @Test\n  public void testCallsLoadFailedAfterTooManyRedirects() throws Exception {\n    for (int i = 0; i < 20; i++) {\n      mockWebServer.enqueue(\n          new MockResponse()\n              .setResponseCode(301)\n              .setHeader(\"Location\", mockWebServer.url(\"/redirect\" + i).toString()));\n    }\n    getFetcher().loadData(Priority.NORMAL, callback);\n    waitForResponseLatch.await();\n    verify(callback).onLoadFailed(isA(VolleyError.class));\n  }\n\n  @Test\n  public void testCallsLoadFailedIfStatusCodeIs500() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(500).setBody(\"error\"));\n    getFetcher().loadData(Priority.NORMAL, callback);\n    waitForResponseLatch.await();\n    verify(callback).onLoadFailed(isA(VolleyError.class));\n  }\n\n  @Test\n  public void testCallsLoadFailedIfStatusCodeIs400() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(400).setBody(\"error\"));\n    getFetcher().loadData(Priority.LOW, callback);\n    waitForResponseLatch.await();\n    verify(callback).onLoadFailed(isA(VolleyError.class));\n  }\n\n  @Test\n  public void testAppliesHeadersInGlideUrl() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200));\n    String headerField = \"field\";\n    String headerValue = \"value\";\n    Map<String, String> headersMap = new HashMap<>();\n    headersMap.put(headerField, headerValue);\n    Headers headers = mock(Headers.class);\n    when(headers.getHeaders()).thenReturn(headersMap);\n\n    getFetcher(headers).loadData(Priority.HIGH, callback);\n    waitForResponseLatch.await();\n\n    assertThat(mockWebServer.takeRequest().getHeader(headerField)).isEqualTo(headerValue);\n  }\n\n  private DataFetcher<InputStream> getFetcher() {\n    return getFetcher(Headers.DEFAULT);\n  }\n\n  private DataFetcher<InputStream> getFetcher(Headers headers) {\n    URL url = mockWebServer.url(DEFAULT_PATH).url();\n    return new VolleyStreamFetcher(requestQueue, new GlideUrl(url.toString(), headers));\n  }\n\n  private class CountDown implements Answer<Void> {\n\n    @Override\n    public Void answer(InvocationOnMock invocation) throws Throwable {\n      waitForResponseLatch.countDown();\n      return null;\n    }\n  }\n\n  /** A shadow clock that doesn't rely on running on an Android thread with a Looper. */\n  @Implements(SystemClock.class)\n  public static class FakeSystemClock extends ShadowSystemClock {\n\n    // Used by Shadow.\n    @SuppressWarnings(\"unused\")\n    @Implementation\n    public static long elapsedRealtime() {\n      // The default is to return something using the main looper, which doesn't exist on\n      // Volley's threads.\n      return System.currentTimeMillis();\n    }\n  }\n}\n"
  },
  {
    "path": "library/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nif (!hasProperty('DISABLE_ERROR_PRONE')) {\n    apply plugin: \"net.ltgt.errorprone\"\n}\n\ntasks.withType(JavaCompile) {\n    options.fork = true\n}\n\ndependencies {\n    api project(':third_party:gif_decoder')\n    api project(':third_party:disklrucache')\n    api project(':annotation')\n    api libs.androidx.fragment\n    api libs.androidx.vectordrawable\n    api libs.androidx.exifinterface\n    api libs.androidx.tracing\n    compileOnly libs.androidx.appcompat\n\n    if (project.plugins.hasPlugin('net.ltgt.errorprone')) {\n        errorprone libs.errorprone.core\n    }\n\n    testImplementation libs.androidx.appcompat\n    testImplementation project(':testutil')\n    testImplementation libs.guava.testlib\n    testImplementation libs.truth\n    testImplementation libs.junit\n    testImplementation libs.mockito.core\n    testImplementation libs.robolectric\n    testImplementation libs.mockwebserver\n    testImplementation libs.androidx.test.core\n    testImplementation libs.androidx.junit\n    testImplementation libs.androidx.test.runner\n}\n\nif (project.plugins.hasPlugin('net.ltgt.errorprone')) {\n    tasks.withType(JavaCompile) {\n        options.errorprone.disable(\n            // It's often useful to track individual objects when debugging\n            // object pooling.\n            \"ObjectToString\",\n            // Doesn't apply when we can't use lambadas.\n            \"UnnecessaryAnonymousClass\",\n            // TODO(judds): Fix these and re-enable this check\n            \"BadImport\",\n            \"UnescapedEntity\",\n            \"MissingSummary\",\n            \"InlineMeSuggester\",\n            \"CanIgnoreReturnValueSuggester\",\n            \"TypeNameShadowing\",\n            \"UndefinedEquals\",\n            \"UnnecessaryParentheses\",\n            \"UnusedVariable\",\n            \"EqualsGetClass\",\n            \"LockNotBeforeTry\")\n    }\n}\n\nandroid {\n    namespace 'com.bumptech.glide'\n    compileSdkVersion libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk libs.versions.min.sdk.version.get() as int\n        targetSdk libs.versions.target.sdk.version.get() as int\n        versionName VERSION_NAME as String\n        consumerProguardFiles 'proguard-rules.txt'\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n\n// Change the name to make it a little more obvious where the main library\n// documentation has gone. Using a capital letter happens to make this first in\n// the list too...\nafterEvaluate {\n    dokkaHtmlPartial.configure {\n        dokkaSourceSets {\n            named(\"main\") {\n                moduleName.set(\"Glide\")\n            }\n        }\n    }\n}\n\ncheck.dependsOn(':library:pmd:pmd')\ncheck.dependsOn(':library:test:check')\n\n// Used in pmd and findbugs subprojects.\n@SuppressWarnings(\"GroovyUnusedDeclaration\")\ndef classPathForQuality() {\n    return files(\n            android.bootClasspath,\n            project.android.libraryVariants.collect { it.javaCompile.classpath }\n    )\n}\n\napply from: \"${rootProject.projectDir}/scripts/upload.gradle.kts\"\n"
  },
  {
    "path": "library/gradle.properties",
    "content": "POM_NAME=Glide\nPOM_ARTIFACT_ID=glide\nPOM_PACKAGING=aar\n\n# Prefix and postfix for source and javadoc jars.\nJAR_PREFIX=glide-\nJAR_POSTFIX=\n"
  },
  {
    "path": "library/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n    <issue id=\"ExifInterface\" severity=\"ignore\"/>\n    <issue id=\"GradleDependency\" severity=\"ignore\"/>\n</lint>\n"
  },
  {
    "path": "library/pmd/build.gradle",
    "content": "apply plugin: 'pmd'\n\ndef library = project(':library')\n\npmd {\n    toolVersion libs.versions.pmd.get()\n}\n\ntasks.create('pmd', Pmd) {\n    dependsOn library.tasks.compileReleaseJavaWithJavac\n    targetJdk = TargetJdk.VERSION_1_7\n\n    description 'Run pmd'\n    group 'verification'\n\n    // If ruleSets is not empty, it seems to contain some\n    // defaults which override rules in the ruleset file...\n    ruleSets = []\n    ruleSetFiles = files(\"${library.projectDir}/pmd-ruleset.xml\")\n    source library.android.sourceSets.main.java.srcDirs\n    classpath = files()\n    classpath += files(library.tasks.compileReleaseJavaWithJavac.destinationDir)\n    doFirst {\n        classpath += library.classPathForQuality()\n    }\n\n    //TODO enable this once new Gradle containing this flag is out\n    //see https://github.com/gradle/gradle/pull/3125#issuecomment-352442432\n    //incrementalAnalysis = true\n\n    // Failures are caught and printed by the violations plugin.\n    ignoreFailures = true\n\n    reports {\n        xml.required.set(true)\n        html.required.set(false)\n    }\n}\n\n"
  },
  {
    "path": "library/pmd-ruleset.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n<ruleset name=\"PMD.rul\" xmlns=\"http://pmd.sourceforge.net/ruleset/2.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd\">\n\n    <description>Check for flaws in Glide's codebase.</description>\n\n    <rule ref=\"category/java/errorprone.xml\">\n        <exclude name=\"AvoidBranchingStatementAsLastInLoop\"/>\n        <!-- Not using beans. -->\n        <exclude name=\"BeanMembersShouldSerialize\" />\n        <!-- wat -->\n        <exclude name=\"AvoidFieldNameMatchingTypeName\" />\n        <!-- This is identifying trivial cases that are clearly correct. -->\n        <exclude name=\"DataflowAnomalyAnalysis\" />\n        <!-- Used regularly for object pooling. -->\n        <exclude name=\"NullAssignment\" />\n        <!-- This can make the code easier to read and avoid duplicated logic in some cases. -->\n        <exclude name=\"AssignmentInOperand\" />\n        <!-- I don't think this is confusing. -->\n        <exclude name=\"AvoidFieldNameMatchingMethodName\" />\n        <!-- There are enough cases where this makes sense (typically related to logic around the number of items in a collection) that a blanket ban doesn't seem like a good idea. -->\n        <exclude name=\"AvoidLiteralsInIfCondition\" />\n        <!-- It's clear that this is bad, but we have a number of cases where it makes sense and a blanket ban is irritating. -->\n        <exclude name=\"AvoidCatchingThrowable\" />\n    </rule>\n    <rule ref=\"category/java/errorprone.xml/AvoidDuplicateLiterals\">\n      <properties>\n        <property name=\"skipAnnotations\" value=\"true\" />\n      </properties>\n    </rule>\n    <rule ref=\"category/java/codestyle.xml\">\n      <!-- Abstract classes don't need to have Abstract in the name -->\n      <exclude name=\"AbstractNaming\" />\n      <!-- Who cares? -->\n      <exclude name=\"AtLeastOneConstructor\" />\n      <!-- Don't need to annotate package private methods. -->\n      <exclude name=\"DefaultPackage\" />\n      <exclude name=\"CommentDefaultAccessModifier\" />\n      <!-- Optionally implemented default empty methods are fine. -->\n      <exclude name=\"EmptyMethodInAbstractClassShouldBeAbstract\" />\n      <!-- Why make generics less clear by using shorter names? -->\n      <exclude name=\"GenericsNaming\" />\n      <!-- No need to enforce final if it's not necessary. -->\n      <exclude name=\"MethodArgumentCouldBeFinal\" />\n      <exclude name=\"LocalVariableCouldBeFinal\" />\n      <!-- This isn't always the easiest way to read a method. -->\n      <exclude name=\"OnlyOneReturn\" />\n      <!-- Obfuscated code is best code? -->\n      <exclude name=\"LongVariable\" />\n      <!-- This is not always true. -->\n      <exclude name=\"ShortClassName\" />\n      <!-- A good idea but we have tons of violations. FIXME. -->\n      <exclude name=\"ShortMethodName\" />\n      <exclude name=\"ShortVariable\" />\n      <!-- We don't use in and out to mean modified or not modified by the method, it's useful to match framework methods. -->\n      <exclude name=\"AvoidPrefixingMethodParameters\" />\n      <!-- No idea what this is supposed to accomplish. -->\n      <exclude name=\"AvoidFinalLocalVariable\" />\n      <!-- These are often useful for clarity and explicitly suggested by Google's code style. -->\n      <exclude name=\"UselessParentheses\" />\n      <!-- Theoretically this might be reasonable but the number of imports probably varies from class to class and this doesn't seem worth the overhead to maintain. -->\n      <exclude name=\"TooManyStaticImports\" />\n      <!-- Lots of existing violations, not clear that the overhead is worthwhile though there are some cases where we definitely need to call super. FIXME. -->\n      <exclude name=\"CallSuperInConstructor\" />\n      <!-- This is a reasonable idea, but in practice often the != null case is the expected case and it makes sense for it to come first. -->\n      <exclude name=\"ConfusingTernary\" />\n    </rule>\n    <rule ref=\"category/java/performance.xml\" >\n      <!-- Android may not behave the same as java VMs, using short can be clearer when working with binary data. -->\n      <exclude name=\"AvoidUsingShortType\" />\n      <!-- The suggsted alternatives are not available until Glide's minsdk level is 26+ -->\n      <exclude name=\"AvoidFileStream\" />\n    </rule>\n    <rule ref=\"category/java/bestpractices.xml\" >\n      <!-- Catches any method, test or not, that has the name \"tearDown\". -->\n      <exclude name=\"JUnit4TestShouldUseAfterAnnotation\" />\n      <!-- This is a good idea, but in practice it's often somewhat clearer than defining a temporary variable and we do it all over the place. -->\n      <exclude name=\"AvoidReassigningParameters\" />\n      <!-- This ignores imports used by javadocs and is worse than the existing checkstyle check. -->\n      <exclude name=\"UnusedImports\" />\n    </rule>\n    <rule ref=\"category/java/bestpractices.xml/OneDeclarationPerLine\">\n      <properties>\n        <property name=\"strictMode\" value=\"true\" />\n        <!-- Allow `for (int i = 0, size = list.size(); i < size; i++) {`\n             Somewhat clearer to set size along with the index. -->\n        <property name=\"violationSuppressXPath\"\n                  value=\"self::LocalVariableDeclaration\n                           [parent::ForInit]\n                           [Type/PrimitiveType[@Image = 'int']\n                             and VariableDeclarator/VariableDeclaratorId[@Image='i']\n                             and VariableDeclarator/VariableDeclaratorId[@Image='size']\n                           ]\n                        \" />\n      </properties>\n    </rule>\n    <rule ref=\"category/java/bestpractices.xml/AccessorMethodGeneration\"\n      message=\"Avoid autogenerated methods to access private fields and methods of inner / outer classes.\n                       Use @Synthetic to flag members made more visible than necessary to prevent accessors.\">\n      <properties>\n        <!-- Ignore references to `private static final * * = <literal>`\n             Suppress via XPath: current node (access that generates the accessor) is .\n             Check if there exists a FieldDeclaration (private static final)\n             which has a VariableInitializer with a Literal\n             and the name (@Image) of the declaration is the same as the accessed member.\n             TODO calculated constants are false positive https://github.com/pmd/pmd/issues/808\n        -->\n        <property name=\"violationSuppressXPath\" value=\"\n                .[@Image =\n                    //FieldDeclaration[@Private = 'true' and @Static='true' and @Final='true']\n                    /VariableDeclarator[\n                        VariableInitializer/Expression/PrimaryExpression[not(PrimarySuffix)]/PrimaryPrefix/Literal\n                    ]/VariableDeclaratorId/@Image\n                 ]\" />\n      </properties>\n    </rule>\n\n    <rule ref=\"category/java/design.xml\">\n        <exclude name=\"GodClass\" />\n        <!-- No idea how you reasonably define this. -->\n        <exclude name=\"ExcessiveImports\" />\n        <exclude name=\"CouplingBetweenObjects\" />\n        <exclude name=\"TooManyMethods\" />\n        <exclude name=\"LawOfDemeter\" />\n        <exclude name=\"NcssCount\" />\n        <exclude name=\"ExcessiveParameterList\" />\n        <exclude name=\"TooManyFields\" />\n        <!-- We don't define any packages to use with this rule. -->\n        <exclude name=\"LoosePackageCoupling\" />\n        <!-- Throwing other types of exceptions doesn't seem to add much to clarify. -->\n        <exclude name=\"AvoidThrowingRawExceptionTypes\" />\n        <exclude name=\"AvoidThrowingNullPointerException\" />\n        <!-- TODO: explore these further. -->\n        <exclude name=\"CyclomaticComplexity\" />\n        <exclude name=\"NPathComplexity\" />\n        <exclude name=\"ExcessiveMethodLength\" />\n        <exclude name=\"ExcessiveClassLength\" />\n        <exclude name=\"ExcessivePublicCount\" />\n        <!-- This is redundant, also caught with AvoidCatchingNPEs. -->\n        <exclude name=\"AvoidCatchingGenericException\" />\n    </rule>\n\n    <rule ref=\"category/java/multithreading.xml\">\n        <exclude name=\"AvoidSynchronizedAtMethodLevel\"/>\n        <!-- This check breaks on double checked locking which is safe in Java 6/7 -->\n        <exclude name=\"NonThreadSafeSingleton\"/>\n        <!-- Used frequently in the singleton pattern. -->\n        <exclude name=\"AvoidUsingVolatile\" />\n        <!-- No reason to do this by default. -->\n        <exclude name=\"UseConcurrentHashMap\" />\n        <exclude name=\"DoNotUseThreads\" />\n    </rule>\n\n    <rule ref=\"category/java/errorprone.xml/EmptyCatchBlock\" message=\"Commented blocks are ok\">\n        <properties>\n            <property name=\"allowCommentedBlocks\" value=\"true\"/>\n        </properties>\n    </rule>\n\n\n  <!-- Configures check to avoid violation when @Synthetic annotation is present. -->\n    <rule ref=\"category/java/documentation.xml/UncommentedEmptyConstructor\">\n      <properties>\n        <property name=\"violationSuppressXPath\"\n          value=\"../Annotation/MarkerAnnotation/Name[@Image='Synthetic']\" />\n      </properties>\n    </rule>\n\n</ruleset>\n"
  },
  {
    "path": "library/proguard-rules.txt",
    "content": "-keep public class * implements com.bumptech.glide.module.GlideModule\n-keep class * extends com.bumptech.glide.module.AppGlideModule {\n <init>(...);\n}\n-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {\n  **[] $VALUES;\n  public *;\n}\n-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {\n  *** rewind();\n}\n\n# Uncomment for DexGuard only\n#-keepresourcexmlelements manifest/application/meta-data@value=GlideModule\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/GeneratedAppGlideModule.java",
    "content": "package com.bumptech.glide;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.manager.RequestManagerRetriever;\nimport com.bumptech.glide.module.AppGlideModule;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Allows {@link AppGlideModule}s to exclude {@link com.bumptech.glide.annotation.GlideModule}s to\n * ease the migration from {@link com.bumptech.glide.annotation.GlideModule}s to Glide's annotation\n * processing system and optionally provides a {@link\n * com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory} impl.\n */\nabstract class GeneratedAppGlideModule extends AppGlideModule {\n  /** This method can be removed when manifest parsing is no longer supported. */\n  @NonNull\n  Set<Class<?>> getExcludedModuleClasses() {\n    return new HashSet<>();\n  }\n\n  @Nullable\n  RequestManagerRetriever.RequestManagerFactory getRequestManagerFactory() {\n    return null;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/GenericTransitionOptions.java",
    "content": "package com.bumptech.glide;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.request.transition.TransitionFactory;\nimport com.bumptech.glide.request.transition.ViewPropertyTransition;\n\n/**\n * Implementation of {@link TransitionOptions} that exposes only generic methods that can be applied\n * to any resource type.\n *\n * @param <TranscodeType> The type of the resource that will be displayed.\n */\n// Public API.\n@SuppressWarnings({\"PMD.UseUtilityClass\", \"unused\"})\npublic final class GenericTransitionOptions<TranscodeType>\n    extends TransitionOptions<GenericTransitionOptions<TranscodeType>, TranscodeType> {\n  /**\n   * Removes any existing animation put on the builder.\n   *\n   * @see GenericTransitionOptions#dontTransition()\n   */\n  @NonNull\n  public static <TranscodeType> GenericTransitionOptions<TranscodeType> withNoTransition() {\n    return new GenericTransitionOptions<TranscodeType>().dontTransition();\n  }\n\n  /**\n   * Returns a typed {@link GenericTransitionOptions} object that uses the given view animation.\n   *\n   * @see GenericTransitionOptions#transition(int)\n   */\n  @NonNull\n  public static <TranscodeType> GenericTransitionOptions<TranscodeType> with(int viewAnimationId) {\n    return new GenericTransitionOptions<TranscodeType>().transition(viewAnimationId);\n  }\n\n  /**\n   * Returns a typed {@link GenericTransitionOptions} object that uses the given animator.\n   *\n   * @see GenericTransitionOptions#transition(ViewPropertyTransition.Animator)\n   */\n  @NonNull\n  public static <TranscodeType> GenericTransitionOptions<TranscodeType> with(\n      @NonNull ViewPropertyTransition.Animator animator) {\n    return new GenericTransitionOptions<TranscodeType>().transition(animator);\n  }\n\n  /**\n   * Returns a typed {@link GenericTransitionOptions} object that uses the given transition factory.\n   *\n   * @see GenericTransitionOptions#transition(TransitionFactory)\n   */\n  @NonNull\n  public static <TranscodeType> GenericTransitionOptions<TranscodeType> with(\n      @NonNull TransitionFactory<? super TranscodeType> transitionFactory) {\n    return new GenericTransitionOptions<TranscodeType>().transition(transitionFactory);\n  }\n\n  // Make sure that we're not equal to any other concrete implementation of TransitionOptions.\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof GenericTransitionOptions && super.equals(o);\n  }\n\n  // Our class doesn't include any additional properties, so we don't need to modify hashcode, but\n  // keep it here as a reminder in case we add properties.\n  @SuppressWarnings(\"PMD.UselessOverridingMethod\")\n  @Override\n  public int hashCode() {\n    return super.hashCode();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/Glide.java",
    "content": "package com.bumptech.glide;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.content.ComponentCallbacks2;\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.graphics.Bitmap;\nimport android.os.Bundle;\nimport android.os.MessageQueue.IdleHandler;\nimport android.util.Log;\nimport android.view.View;\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.engine.Engine;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.load.engine.prefill.BitmapPreFiller;\nimport com.bumptech.glide.load.engine.prefill.PreFillType;\nimport com.bumptech.glide.load.engine.prefill.PreFillType.Builder;\nimport com.bumptech.glide.load.resource.bitmap.Downsampler;\nimport com.bumptech.glide.load.resource.bitmap.HardwareConfigState;\nimport com.bumptech.glide.manager.ConnectivityMonitorFactory;\nimport com.bumptech.glide.manager.RequestManagerRetriever;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.module.GlideModule;\nimport com.bumptech.glide.module.ManifestParser;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.ImageViewTargetFactory;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.GlideSuppliers;\nimport com.bumptech.glide.util.GlideSuppliers.GlideSupplier;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Util;\nimport java.io.File;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * A singleton to present a simple static interface for building requests with {@link\n * RequestBuilder} and maintaining an {@link Engine}, {@link BitmapPool}, {@link\n * com.bumptech.glide.load.engine.cache.DiskCache} and {@link MemoryCache}.\n */\npublic class Glide implements ComponentCallbacks2 {\n  private static final String DEFAULT_DISK_CACHE_DIR = \"image_manager_disk_cache\";\n  private static final String DESTROYED_ACTIVITY_WARNING =\n      \"You cannot start a load on a not yet attached View or a Fragment where getActivity() \"\n          + \"returns null (which usually occurs when getActivity() is called before the Fragment \"\n          + \"is attached or after the Fragment is destroyed).\";\n  private static final String TAG = \"Glide\";\n\n  @GuardedBy(\"Glide.class\")\n  private static volatile Glide glide;\n\n  private static volatile boolean isInitializing;\n\n  private final Engine engine;\n  private final BitmapPool bitmapPool;\n  private final MemoryCache memoryCache;\n  private final GlideContext glideContext;\n  private final ArrayPool arrayPool;\n  private final RequestManagerRetriever requestManagerRetriever;\n  private final ConnectivityMonitorFactory connectivityMonitorFactory;\n\n  @GuardedBy(\"managers\")\n  private final List<RequestManager> managers = new ArrayList<>();\n\n  private final RequestOptionsFactory defaultRequestOptionsFactory;\n  private MemoryCategory memoryCategory = MemoryCategory.NORMAL;\n\n  @GuardedBy(\"this\")\n  @Nullable\n  private BitmapPreFiller bitmapPreFiller;\n\n  private boolean inBackground = false;\n  private MemoryCategory memoryCategoryInBackground = null;\n  private MemoryCategory memoryCategoryInForeground = MemoryCategory.NORMAL;\n\n  private final GlideSupplier<SetMemoryCategoryOnLifecycleCallbacks> setMemoryCategoryCallbacks =\n      GlideSuppliers.memorize(SetMemoryCategoryOnLifecycleCallbacks::new);\n\n  /**\n   * Returns a directory with a default name in the private cache directory of the application to\n   * use to store retrieved media and thumbnails.\n   *\n   * @param context A context.\n   * @see #getPhotoCacheDir(android.content.Context, String)\n   */\n  @Nullable\n  public static File getPhotoCacheDir(@NonNull Context context) {\n    return getPhotoCacheDir(context, DEFAULT_DISK_CACHE_DIR);\n  }\n\n  /**\n   * Returns a directory with the given name in the private cache directory of the application to\n   * use to store retrieved media and thumbnails.\n   *\n   * @param context A context.\n   * @param cacheName The name of the subdirectory in which to store the cache.\n   * @see #getPhotoCacheDir(android.content.Context)\n   */\n  @Nullable\n  public static File getPhotoCacheDir(@NonNull Context context, @NonNull String cacheName) {\n    File cacheDir = context.getCacheDir();\n    if (cacheDir != null) {\n      File result = new File(cacheDir, cacheName);\n      if (result.isDirectory() || result.mkdirs()) {\n        return result;\n      }\n      // File wasn't able to create a directory, or the result exists but not a directory\n      return null;\n    }\n    if (Log.isLoggable(TAG, Log.ERROR)) {\n      Log.e(TAG, \"default disk cache dir is null\");\n    }\n    return null;\n  }\n\n  /**\n   * Get the singleton.\n   *\n   * @return the singleton\n   */\n  @NonNull\n  // Double checked locking is safe here.\n  @SuppressWarnings(\"GuardedBy\")\n  public static Glide get(@NonNull Context context) {\n    if (glide == null) {\n      GeneratedAppGlideModule annotationGeneratedModule =\n          getAnnotationGeneratedGlideModules(context.getApplicationContext());\n      synchronized (Glide.class) {\n        if (glide == null) {\n          checkAndInitializeGlide(context, annotationGeneratedModule);\n        }\n      }\n    }\n\n    return glide;\n  }\n\n  @GuardedBy(\"Glide.class\")\n  @VisibleForTesting\n  static void checkAndInitializeGlide(\n      @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) {\n    // In the thread running initGlide(), one or more classes may call Glide.get(context).\n    // Without this check, those calls could trigger infinite recursion.\n    if (isInitializing) {\n      throw new IllegalStateException(\n          \"Glide has been called recursively, this is probably an internal library error!\");\n    }\n    isInitializing = true;\n    try {\n      initializeGlide(context, generatedAppGlideModule);\n    } finally {\n      isInitializing = false;\n    }\n  }\n\n  /**\n   * @deprecated Use {@link #init(Context, GlideBuilder)} to get a singleton compatible with Glide's\n   *     generated API.\n   *     <p>This method will be removed in a future version of Glide.\n   */\n  @VisibleForTesting\n  @Deprecated\n  public static synchronized void init(Glide glide) {\n    if (Glide.glide != null) {\n      tearDown();\n    }\n    Glide.glide = glide;\n  }\n\n  @VisibleForTesting\n  public static void init(@NonNull Context context, @NonNull GlideBuilder builder) {\n    GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(context);\n    synchronized (Glide.class) {\n      if (Glide.glide != null) {\n        tearDown();\n      }\n      initializeGlide(context, builder, annotationGeneratedModule);\n    }\n  }\n\n  @VisibleForTesting\n  public static synchronized boolean isInitialized() {\n    return glide != null;\n  }\n\n  /**\n   * Allows hardware Bitmaps to be used prior to the first frame in the app being drawn as soon as\n   * this method is called.\n   *\n   * <p>If you use this method in non-test code, your app will experience native crashes on some\n   * versions of Android if you try to decode a hardware Bitmap. This method is only useful for\n   * testing.\n   */\n  @VisibleForTesting\n  public static void enableHardwareBitmaps() {\n    HardwareConfigState.getInstance().unblockHardwareBitmaps();\n  }\n\n  @VisibleForTesting\n  public static void tearDown() {\n    synchronized (Glide.class) {\n      if (glide != null) {\n        glide.getContext().getApplicationContext().unregisterComponentCallbacks(glide);\n        glide.unregisterActivityLifecycleCallbacks();\n        glide.engine.shutdown();\n      }\n      glide = null;\n    }\n  }\n\n  @GuardedBy(\"Glide.class\")\n  private static void initializeGlide(\n      @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) {\n    initializeGlide(context, new GlideBuilder(), generatedAppGlideModule);\n  }\n\n  @GuardedBy(\"Glide.class\")\n  @SuppressWarnings(\"deprecation\")\n  private static void initializeGlide(\n      @NonNull Context context,\n      @NonNull GlideBuilder builder,\n      @Nullable GeneratedAppGlideModule annotationGeneratedModule) {\n    Context applicationContext = context.getApplicationContext();\n    List<GlideModule> manifestModules = Collections.emptyList();\n    if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {\n      manifestModules = new ManifestParser(applicationContext).parse();\n    }\n\n    if (annotationGeneratedModule != null\n        && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {\n      Set<Class<?>> excludedModuleClasses = annotationGeneratedModule.getExcludedModuleClasses();\n      Iterator<GlideModule> iterator = manifestModules.iterator();\n      while (iterator.hasNext()) {\n        GlideModule current = iterator.next();\n        if (!excludedModuleClasses.contains(current.getClass())) {\n          continue;\n        }\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"AppGlideModule excludes manifest GlideModule: \" + current);\n        }\n        iterator.remove();\n      }\n    }\n\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      for (GlideModule glideModule : manifestModules) {\n        Log.d(TAG, \"Discovered GlideModule from manifest: \" + glideModule.getClass());\n      }\n    }\n\n    RequestManagerRetriever.RequestManagerFactory factory =\n        annotationGeneratedModule != null\n            ? annotationGeneratedModule.getRequestManagerFactory()\n            : null;\n    builder.setRequestManagerFactory(factory);\n    for (GlideModule module : manifestModules) {\n      module.applyOptions(applicationContext, builder);\n    }\n    if (annotationGeneratedModule != null) {\n      annotationGeneratedModule.applyOptions(applicationContext, builder);\n    }\n    Glide glide = builder.build(applicationContext, manifestModules, annotationGeneratedModule);\n    applicationContext.registerComponentCallbacks(glide);\n    glide.registerActivityLifecycleCallbacks();\n    Glide.glide = glide;\n  }\n\n  @Nullable\n  @SuppressWarnings({\"unchecked\", \"TryWithIdenticalCatches\", \"PMD.UnusedFormalParameter\"})\n  private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules(Context context) {\n    GeneratedAppGlideModule result = null;\n    try {\n      Class<GeneratedAppGlideModule> clazz =\n          (Class<GeneratedAppGlideModule>)\n              Class.forName(\"com.bumptech.glide.GeneratedAppGlideModuleImpl\");\n      result =\n          clazz.getDeclaredConstructor(Context.class).newInstance(context.getApplicationContext());\n    } catch (ClassNotFoundException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(\n            TAG,\n            \"Failed to find GeneratedAppGlideModule. You should include an\"\n                + \" annotationProcessor compile dependency on com.github.bumptech.glide:compiler\"\n                + \" in your application and a @GlideModule annotated AppGlideModule implementation\"\n                + \" or LibraryGlideModules will be silently ignored\");\n      }\n      // These exceptions can't be squashed across all versions of Android.\n    } catch (InstantiationException e) {\n      throwIncorrectGlideModule(e);\n    } catch (IllegalAccessException e) {\n      throwIncorrectGlideModule(e);\n    } catch (NoSuchMethodException e) {\n      throwIncorrectGlideModule(e);\n    } catch (InvocationTargetException e) {\n      throwIncorrectGlideModule(e);\n    }\n    return result;\n  }\n\n  private static void throwIncorrectGlideModule(Exception e) {\n    throw new IllegalStateException(\n        \"GeneratedAppGlideModuleImpl is implemented incorrectly.\"\n            + \" If you've manually implemented this class, remove your implementation. The\"\n            + \" Annotation processor will generate a correct implementation.\",\n        e);\n  }\n\n  @SuppressWarnings(\"PMD.UnusedFormalParameter\")\n  Glide(\n      @NonNull Context context,\n      @NonNull Engine engine,\n      @NonNull MemoryCache memoryCache,\n      @NonNull BitmapPool bitmapPool,\n      @NonNull ArrayPool arrayPool,\n      @NonNull RequestManagerRetriever requestManagerRetriever,\n      @NonNull ConnectivityMonitorFactory connectivityMonitorFactory,\n      int logLevel,\n      @NonNull RequestOptionsFactory defaultRequestOptionsFactory,\n      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,\n      @NonNull List<RequestListener<Object>> defaultRequestListeners,\n      @NonNull List<GlideModule> manifestModules,\n      @Nullable AppGlideModule annotationGeneratedModule,\n      @NonNull GlideExperiments experiments) {\n    this.engine = engine;\n    this.bitmapPool = bitmapPool;\n    this.arrayPool = arrayPool;\n    this.memoryCache = memoryCache;\n    this.requestManagerRetriever = requestManagerRetriever;\n    this.connectivityMonitorFactory = connectivityMonitorFactory;\n    this.defaultRequestOptionsFactory = defaultRequestOptionsFactory;\n\n    GlideBuilder.MemoryCategoryInBackground memoryCategoryInBackground =\n        experiments.get(GlideBuilder.MemoryCategoryInBackground.class);\n    if (memoryCategoryInBackground != null) {\n      this.memoryCategoryInBackground = memoryCategoryInBackground.value();\n    }\n\n    // This has a circular relationship with Glide and GlideContext in that it depends on both,\n    // but it's created by Glide's constructor. In practice this shouldn't matter because the\n    // supplier holding the registry should never be initialized before this constructor finishes.\n    GlideSupplier<Registry> registry =\n        RegistryFactory.lazilyCreateAndInitializeRegistry(\n            this, manifestModules, annotationGeneratedModule);\n\n    ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();\n    glideContext =\n        new GlideContext(\n            context,\n            arrayPool,\n            registry,\n            imageViewTargetFactory,\n            defaultRequestOptionsFactory,\n            defaultTransitionOptions,\n            defaultRequestListeners,\n            engine,\n            experiments,\n            logLevel);\n  }\n\n  /**\n   * Returns the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} used to\n   * temporarily store {@link android.graphics.Bitmap}s so they can be reused to avoid garbage\n   * collections.\n   *\n   * <p>Note - Using this pool directly can lead to undefined behavior and strange drawing errors.\n   * Any {@link android.graphics.Bitmap} added to the pool must not be currently in use in any other\n   * part of the application. Any {@link android.graphics.Bitmap} added to the pool must be removed\n   * from the pool before it is added a second time.\n   *\n   * <p>Note - To make effective use of the pool, any {@link android.graphics.Bitmap} removed from\n   * the pool must eventually be re-added. Otherwise the pool will eventually empty and will not\n   * serve any useful purpose.\n   *\n   * <p>The primary reason this object is exposed is for use in custom {@link\n   * com.bumptech.glide.load.ResourceDecoder}s and {@link com.bumptech.glide.load.Transformation}s.\n   * Use outside of these classes is not generally recommended.\n   */\n  @NonNull\n  public BitmapPool getBitmapPool() {\n    return bitmapPool;\n  }\n\n  @NonNull\n  public ArrayPool getArrayPool() {\n    return arrayPool;\n  }\n\n  /**\n   * @return The context associated with this instance.\n   */\n  @NonNull\n  public Context getContext() {\n    return glideContext.getBaseContext();\n  }\n\n  ConnectivityMonitorFactory getConnectivityMonitorFactory() {\n    return connectivityMonitorFactory;\n  }\n\n  @NonNull\n  GlideContext getGlideContext() {\n    return glideContext;\n  }\n\n  /**\n   * Pre-fills the {@link BitmapPool} using the given sizes.\n   *\n   * <p>Enough Bitmaps are added to completely fill the pool, so most or all of the Bitmaps\n   * currently in the pool will be evicted. Bitmaps are allocated according to the weights of the\n   * given sizes, where each size gets (weight / prefillWeightSum) percent of the pool to fill.\n   *\n   * <p>Note - Pre-filling is done asynchronously using and {@link IdleHandler}. Any currently\n   * running pre-fill will be cancelled and replaced by a call to this method.\n   *\n   * <p>This method should be used with caution, overly aggressive pre-filling is substantially\n   * worse than not pre-filling at all. Pre-filling should only be started in onCreate to avoid\n   * constantly clearing and re-filling the {@link BitmapPool}. Rotation should be carefully\n   * considered as well. It may be worth calling this method only when no saved instance state\n   * exists so that pre-filling only happens when the Activity is first created, rather than on\n   * every rotation.\n   *\n   * @param bitmapAttributeBuilders The list of {@link Builder Builders} representing individual\n   *     sizes and configurations of {@link Bitmap}s to be pre-filled.\n   */\n  @SuppressWarnings(\"unused\") // Public API\n  public synchronized void preFillBitmapPool(\n      @NonNull PreFillType.Builder... bitmapAttributeBuilders) {\n    if (bitmapPreFiller == null) {\n      DecodeFormat decodeFormat =\n          defaultRequestOptionsFactory.build().getOptions().get(Downsampler.DECODE_FORMAT);\n      bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat);\n    }\n\n    bitmapPreFiller.preFill(bitmapAttributeBuilders);\n  }\n\n  /**\n   * Clears as much memory as possible.\n   *\n   * @see android.content.ComponentCallbacks#onLowMemory()\n   * @see android.content.ComponentCallbacks2#onLowMemory()\n   */\n  public void clearMemory() {\n    // Engine asserts this anyway when removing resources, fail faster and consistently\n    Util.assertMainThread();\n    // memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.\n    memoryCache.clearMemory();\n    bitmapPool.clearMemory();\n    arrayPool.clearMemory();\n  }\n\n  /**\n   * Clears some memory with the exact amount depending on the given level.\n   *\n   * @see android.content.ComponentCallbacks2#onTrimMemory(int)\n   */\n  public void trimMemory(int level) {\n    // Engine asserts this anyway when removing resources, fail faster and consistently\n    Util.assertMainThread();\n    // Request managers need to be trimmed before the caches and pools, in order for the latter to\n    // have the most benefit.\n    synchronized (managers) {\n      for (RequestManager manager : managers) {\n        manager.onTrimMemory(level);\n      }\n    }\n    // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.\n    memoryCache.trimMemory(level);\n    bitmapPool.trimMemory(level);\n    arrayPool.trimMemory(level);\n  }\n\n  /**\n   * Clears disk cache.\n   *\n   * <p>This method should always be called on a background thread, since it is a blocking call.\n   */\n  // Public API.\n  @SuppressWarnings({\"unused\", \"WeakerAccess\"})\n  public void clearDiskCache() {\n    Util.assertBackgroundThread();\n    engine.clearDiskCache();\n  }\n\n  /** Internal method. */\n  @NonNull\n  public RequestManagerRetriever getRequestManagerRetriever() {\n    return requestManagerRetriever;\n  }\n\n  /**\n   * Adjusts Glide's current and maximum memory usage based on the given {@link MemoryCategory}.\n   *\n   * <p>The default {@link MemoryCategory} is {@link MemoryCategory#NORMAL}. {@link\n   * MemoryCategory#HIGH} increases Glide's maximum memory usage by up to 50% and {@link\n   * MemoryCategory#LOW} decreases Glide's maximum memory usage by 50%. This method should be used\n   * to temporarily increase or decrease memory usage for a single Activity or part of the app. Use\n   * {@link GlideBuilder#setMemoryCache(MemoryCache)} to put a permanent memory size if you want to\n   * change the default.\n   *\n   * @return the previous MemoryCategory used by Glide.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  public MemoryCategory setMemoryCategory(@NonNull MemoryCategory memoryCategory) {\n    // Engine asserts this anyway when removing resources, fail faster and consistently\n    Util.assertMainThread();\n    // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.\n    memoryCache.setSizeMultiplier(memoryCategory.getMultiplier());\n    bitmapPool.setSizeMultiplier(memoryCategory.getMultiplier());\n    MemoryCategory oldCategory = this.memoryCategory;\n    this.memoryCategory = memoryCategory;\n    return oldCategory;\n  }\n\n  @NonNull\n  private static RequestManagerRetriever getRetriever(@Nullable Context context) {\n    // Context could be null for other reasons (ie the user passes in null), but in practice it will\n    // only occur due to errors with the Fragment lifecycle.\n    Preconditions.checkNotNull(context, DESTROYED_ACTIVITY_WARNING);\n    return Glide.get(context).getRequestManagerRetriever();\n  }\n\n  /**\n   * Begin a load with Glide by passing in a context.\n   *\n   * <p>Any requests started using a context will only have the application level options applied\n   * and will not be started or stopped based on lifecycle events. In general, loads should be\n   * started at the level the result will be used in. If the resource will be used in a view in a\n   * child fragment, the load should be started with {@link #with(android.app.Fragment)}} using that\n   * child fragment. Similarly, if the resource will be used in a view in the parent fragment, the\n   * load should be started with {@link #with(android.app.Fragment)} using the parent fragment. In\n   * the same vein, if the resource will be used in a view in an activity, the load should be\n   * started with {@link #with(android.app.Activity)}}.\n   *\n   * <p>This method is appropriate for resources that will be used outside of the normal fragment or\n   * activity lifecycle (For example in services, or for notification thumbnails).\n   *\n   * @param context Any context, will not be retained.\n   * @return A RequestManager for the top level application that can be used to start a load.\n   * @see #with(android.app.Activity)\n   * @see #with(android.app.Fragment)\n   * @see #with(androidx.fragment.app.Fragment)\n   * @see #with(androidx.fragment.app.FragmentActivity)\n   */\n  @NonNull\n  public static RequestManager with(@NonNull Context context) {\n    return getRetriever(context).get(context);\n  }\n\n  /**\n   * Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle\n   * and that uses the given {@link Activity}'s default options.\n   *\n   * @param activity The activity to use.\n   * @return A RequestManager for the given activity that can be used to start a load.\n   * @deprecated This is equivalent to calling {@link #with(Context)} using the application context.\n   *     Use the androidx Activity class instead (ie {@link FragmentActivity}, or {@link\n   *     androidx.appcompat.app.AppCompatActivity}).\n   * @throws IllegalArgumentException if the activity associated with the Glide request is being\n   *     destroyed.\n   */\n  @NonNull\n  @Deprecated\n  public static RequestManager with(@NonNull Activity activity) {\n    return with(activity.getApplicationContext());\n  }\n\n  /**\n   * Begin a load with Glide that will tied to the give {@link\n   * androidx.fragment.app.FragmentActivity}'s lifecycle and that uses the given {@link\n   * androidx.fragment.app.FragmentActivity}'s default options.\n   *\n   * @param activity The activity to use. The activity must not be destroyed.\n   * @return A RequestManager for the given FragmentActivity that can be used to start a load.\n   * @throws IllegalArgumentException if the activity is being destroyed.\n   */\n  @NonNull\n  public static RequestManager with(@NonNull FragmentActivity activity) {\n    return getRetriever(activity).get(activity);\n  }\n\n  /**\n   * Begin a load with Glide that will be tied to the given {@link androidx.fragment.app.Fragment}'s\n   * lifecycle and that uses the given {@link androidx.fragment.app.Fragment}'s default options.\n   *\n   * @param fragment The fragment to use.\n   * @return A RequestManager for the given Fragment that can be used to start a load.\n   * @throws IllegalArgumentException if the activity associated with the fragment is destroyed.\n   */\n  @NonNull\n  public static RequestManager with(@NonNull Fragment fragment) {\n    return getRetriever(fragment.getContext()).get(fragment);\n  }\n\n  /**\n   * Begin a load with Glide that will be tied to the given {@link android.app.Fragment}'s lifecycle\n   * and that uses the given {@link android.app.Fragment}'s default options.\n   *\n   * @param fragment The fragment to use.\n   * @return A RequestManager for the given Fragment that can be used to start a load.\n   * @deprecated This method is identical to calling {@link Glide#with(Context)} using the\n   *     application context. Prefer support Fragments and {@link #with(Fragment)} instead. See\n   *     https://github.com/android/android-ktx/pull/161#issuecomment-363270555.\n   * @throws IllegalArgumentException if the activity associated with the fragment is destroyed.\n   */\n  @Deprecated\n  @NonNull\n  public static RequestManager with(@NonNull android.app.Fragment fragment) {\n    Activity activity = fragment.getActivity();\n    Preconditions.checkNotNull(activity, DESTROYED_ACTIVITY_WARNING);\n    return with(activity.getApplicationContext());\n  }\n\n  /**\n   * Begin a load with Glide that will be tied to the lifecycle of the {@link Fragment}, {@link\n   * android.app.Fragment}, or {@link Activity} that contains the View.\n   *\n   * <p>A {@link Fragment} or {@link android.app.Fragment} is assumed to contain a View if the View\n   * is a child of the View returned by the {@link Fragment#getView()}} method.\n   *\n   * <p>This method will not work if the View is not attached. Prefer the Activity and Fragment\n   * variants unless you're loading in a View subclass.\n   *\n   * <p>This method may be inefficient aways and is definitely inefficient for large hierarchies.\n   * Consider memoizing the result after the View is attached or again, prefer the Activity and\n   * Fragment variants whenever possible.\n   *\n   * <p>When used in Applications that use the non-support {@link android.app.Fragment} classes,\n   * calling this method will produce noisy logs from {@link android.app.FragmentManager}. Consider\n   * avoiding entirely or using the {@link Fragment}s from the support library instead.\n   *\n   * <p>If the support {@link FragmentActivity} class is used, this method will only attempt to\n   * discover support {@link Fragment}s. Any non-support {@link android.app.Fragment}s attached to\n   * the {@link FragmentActivity} will be ignored.\n   *\n   * @param view The view to search for a containing Fragment or Activity from.\n   * @return A RequestManager that can be used to start a load.\n   * @throws IllegalArgumentException if the activity associated with the view is destroyed.\n   */\n  @NonNull\n  public static RequestManager with(@NonNull View view) {\n    return getRetriever(view.getContext()).get(view);\n  }\n\n  @NonNull\n  public Registry getRegistry() {\n    return glideContext.getRegistry();\n  }\n\n  boolean removeFromManagers(@NonNull Target<?> target) {\n    synchronized (managers) {\n      for (RequestManager requestManager : managers) {\n        if (requestManager.untrack(target)) {\n          return true;\n        }\n      }\n    }\n\n    return false;\n  }\n\n  void registerRequestManager(RequestManager requestManager) {\n    synchronized (managers) {\n      if (managers.contains(requestManager)) {\n        throw new IllegalStateException(\"Cannot register already registered manager\");\n      }\n      managers.add(requestManager);\n    }\n  }\n\n  void unregisterRequestManager(RequestManager requestManager) {\n    synchronized (managers) {\n      if (!managers.contains(requestManager)) {\n        throw new IllegalStateException(\"Cannot unregister not yet registered manager\");\n      }\n      managers.remove(requestManager);\n    }\n  }\n\n  @Override\n  public void onTrimMemory(int level) {\n    trimMemory(level);\n    // when level is TRIM_MEMORY_UI_HIDDEN or higher, it indicates that the app is\n    // in the background, limit the memory usage by memoryCategoryInBackground.\n    if (level >= TRIM_MEMORY_UI_HIDDEN) {\n      setMemoryCategoryWhenInBackground();\n    }\n  }\n\n  @Override\n  public void onConfigurationChanged(Configuration newConfig) {\n    // Do nothing.\n  }\n\n  @Override\n  public void onLowMemory() {\n    clearMemory();\n  }\n\n  /** Creates a new instance of {@link RequestOptions}. */\n  public interface RequestOptionsFactory {\n\n    /** Returns a non-null {@link RequestOptions} object. */\n    @NonNull\n    RequestOptions build();\n  }\n\n  private void registerActivityLifecycleCallbacks() {\n    if (memoryCategoryInBackground != null) {\n      Context context = getContext().getApplicationContext();\n      if (!(context instanceof Application) && Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(\n            TAG,\n            \"Glide requires an Application Context. You passed: \"\n                + context\n                + \". This will disable setting memory category in background.\");\n        return;\n      }\n      ((Application) context).registerActivityLifecycleCallbacks(setMemoryCategoryCallbacks.get());\n    }\n  }\n\n  private void unregisterActivityLifecycleCallbacks() {\n    if (memoryCategoryInBackground != null) {\n      Context context = getContext().getApplicationContext();\n      if (context instanceof Application) {\n        ((Application) context)\n            .unregisterActivityLifecycleCallbacks(setMemoryCategoryCallbacks.get());\n      }\n    }\n  }\n\n  private void setMemoryCategoryWhenInBackground() {\n    if (memoryCategoryInBackground == null || inBackground) {\n      return;\n    }\n    inBackground = true;\n    memoryCategoryInForeground = setMemoryCategory(memoryCategoryInBackground);\n  }\n\n  private void setMemoryCategoryWhenInForeground() {\n    if (memoryCategoryInBackground == null || !inBackground) {\n      return;\n    }\n    inBackground = false;\n    setMemoryCategory(memoryCategoryInForeground);\n  }\n\n  private final class SetMemoryCategoryOnLifecycleCallbacks\n      implements Application.ActivityLifecycleCallbacks {\n    @Override\n    public void onActivityStarted(Activity activity) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onActivityResumed(Activity activity) {\n      // Any activity resumed indicates that the app is no longer in the background,\n      // and we should restore the memory usage to normal.\n      setMemoryCategoryWhenInForeground();\n    }\n\n    @Override\n    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onActivityDestroyed(Activity activity) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onActivityStopped(Activity activity) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onActivityPaused(Activity activity) {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/GlideBuilder.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArrayMap;\nimport com.bumptech.glide.Glide.RequestOptionsFactory;\nimport com.bumptech.glide.GlideExperiments.Experiment;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.Engine;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;\nimport com.bumptech.glide.load.engine.cache.LruResourceCache;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.load.engine.cache.MemorySizeCalculator;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.manager.ConnectivityMonitorFactory;\nimport com.bumptech.glide.manager.DefaultConnectivityMonitorFactory;\nimport com.bumptech.glide.manager.RequestManagerRetriever;\nimport com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.module.GlideModule;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/** A builder class for setting default structural classes for Glide to use. */\n@SuppressWarnings(\"PMD.ImmutableField\")\npublic final class GlideBuilder {\n  private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions = new ArrayMap<>();\n  private final GlideExperiments.Builder glideExperimentsBuilder = new GlideExperiments.Builder();\n  private Engine engine;\n  private BitmapPool bitmapPool;\n  private ArrayPool arrayPool;\n  private MemoryCache memoryCache;\n  private GlideExecutor sourceExecutor;\n  private GlideExecutor diskCacheExecutor;\n  private DiskCache.Factory diskCacheFactory;\n  private MemorySizeCalculator memorySizeCalculator;\n  private ConnectivityMonitorFactory connectivityMonitorFactory;\n  private int logLevel = Log.INFO;\n  private RequestOptionsFactory defaultRequestOptionsFactory =\n      new RequestOptionsFactory() {\n        @NonNull\n        @Override\n        public RequestOptions build() {\n          return new RequestOptions();\n        }\n      };\n  @Nullable private RequestManagerFactory requestManagerFactory;\n  private GlideExecutor animationExecutor;\n  private boolean isActiveResourceRetentionAllowed;\n  @Nullable private List<RequestListener<Object>> defaultRequestListeners;\n\n  /**\n   * Sets the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation to use\n   * to store and retrieve reused {@link android.graphics.Bitmap}s.\n   *\n   * @param bitmapPool The pool to use.\n   * @return This builder.\n   */\n  @NonNull\n  public GlideBuilder setBitmapPool(@Nullable BitmapPool bitmapPool) {\n    this.bitmapPool = bitmapPool;\n    return this;\n  }\n\n  /**\n   * Sets the {@link ArrayPool} implementation to allow variable sized arrays to be stored and\n   * retrieved as needed.\n   *\n   * @param arrayPool The pool to use.\n   * @return This builder.\n   */\n  @NonNull\n  public GlideBuilder setArrayPool(@Nullable ArrayPool arrayPool) {\n    this.arrayPool = arrayPool;\n    return this;\n  }\n\n  /**\n   * Sets the {@link com.bumptech.glide.load.engine.cache.MemoryCache} implementation to store\n   * {@link com.bumptech.glide.load.engine.Resource}s that are not currently in use.\n   *\n   * @param memoryCache The cache to use.\n   * @return This builder.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @NonNull\n  public GlideBuilder setMemoryCache(@Nullable MemoryCache memoryCache) {\n    this.memoryCache = memoryCache;\n    return this;\n  }\n\n  /**\n   * Sets the {@link com.bumptech.glide.load.engine.cache.DiskCache.Factory} implementation to use\n   * to construct the {@link com.bumptech.glide.load.engine.cache.DiskCache} to use to store {@link\n   * com.bumptech.glide.load.engine.Resource} data on disk.\n   *\n   * @param diskCacheFactory The disk cache factory to use.\n   * @return This builder.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @NonNull\n  public GlideBuilder setDiskCache(@Nullable DiskCache.Factory diskCacheFactory) {\n    this.diskCacheFactory = diskCacheFactory;\n    return this;\n  }\n\n  /**\n   * Sets the {@link GlideExecutor} to use when retrieving {@link\n   * com.bumptech.glide.load.engine.Resource}s that are not already in the cache.\n   *\n   * <p>The thread count defaults to the number of cores available on the device, with a maximum of\n   * 4.\n   *\n   * <p>Use the {@link GlideExecutor#newSourceExecutor()} methods if you'd like to specify options\n   * for the source executor.\n   *\n   * @param service The ExecutorService to use.\n   * @return This builder.\n   * @see #setDiskCacheExecutor(GlideExecutor)\n   * @see GlideExecutor\n   * @deprecated Use {@link #setSourceExecutor(GlideExecutor)}\n   */\n  @Deprecated\n  public GlideBuilder setResizeExecutor(@Nullable GlideExecutor service) {\n    return setSourceExecutor(service);\n  }\n\n  /**\n   * Sets the {@link GlideExecutor} to use when retrieving {@link\n   * com.bumptech.glide.load.engine.Resource}s that are not already in the cache.\n   *\n   * <p>The thread count defaults to the number of cores available on the device, with a maximum of\n   * 4.\n   *\n   * <p>Use the {@link GlideExecutor#newSourceExecutor()} methods if you'd like to specify options\n   * for the source executor.\n   *\n   * @param service The ExecutorService to use.\n   * @return This builder.\n   * @see #setDiskCacheExecutor(GlideExecutor)\n   * @see GlideExecutor\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @NonNull\n  public GlideBuilder setSourceExecutor(@Nullable GlideExecutor service) {\n    this.sourceExecutor = service;\n    return this;\n  }\n\n  /**\n   * Sets the {@link GlideExecutor} to use when retrieving {@link\n   * com.bumptech.glide.load.engine.Resource}s that are currently in Glide's disk caches.\n   *\n   * <p>Defaults to a single thread which is usually the best combination of memory usage, jank, and\n   * performance, even on high end devices.\n   *\n   * <p>Use the {@link GlideExecutor#newDiskCacheExecutor()} if you'd like to specify options for\n   * the disk cache executor.\n   *\n   * @param service The {@link GlideExecutor} to use.\n   * @return This builder.\n   * @see #setSourceExecutor(GlideExecutor)\n   * @see GlideExecutor\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @NonNull\n  public GlideBuilder setDiskCacheExecutor(@Nullable GlideExecutor service) {\n    this.diskCacheExecutor = service;\n    return this;\n  }\n\n  /**\n   * Sets the {@link GlideExecutor} to use when loading frames of animated images and particularly\n   * of {@link com.bumptech.glide.load.resource.gif.GifDrawable}s.\n   *\n   * <p>Defaults to one or two threads, depending on the number of cores available.\n   *\n   * <p>Use the {@link GlideExecutor#newAnimationExecutor()} methods if you'd like to specify\n   * options for the animation executor.\n   *\n   * @param service The {@link GlideExecutor} to use.\n   * @return This builder.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @NonNull\n  public GlideBuilder setAnimationExecutor(@Nullable GlideExecutor service) {\n    this.animationExecutor = service;\n    return this;\n  }\n\n  /**\n   * Sets the default {@link RequestOptions} to use for all loads across the app.\n   *\n   * <p>Applying additional options with {@link RequestBuilder#apply(BaseRequestOptions)} will\n   * override defaults set here.\n   *\n   * @see #setDefaultRequestOptions(RequestOptionsFactory)\n   * @param requestOptions The options to use by default.\n   * @return This builder.\n   */\n  @NonNull\n  public GlideBuilder setDefaultRequestOptions(@Nullable final RequestOptions requestOptions) {\n    return setDefaultRequestOptions(\n        new RequestOptionsFactory() {\n          @NonNull\n          @Override\n          public RequestOptions build() {\n            return requestOptions != null ? requestOptions : new RequestOptions();\n          }\n        });\n  }\n\n  /**\n   * Sets a factory for the default {@link RequestOptions} to use for all loads across the app and\n   * returns this {@code GlideBuilder}.\n   *\n   * <p>This factory will <em>NOT</em> be called once per load. Instead it will be called a handful\n   * of times and memoized. It's not safe to assume that this factory will be called again for every\n   * new load.\n   *\n   * <p>Applying additional options with {@link RequestBuilder#apply(BaseRequestOptions)} will\n   * override defaults set here.\n   *\n   * @see #setDefaultRequestOptions(RequestOptionsFactory)\n   */\n  @NonNull\n  public GlideBuilder setDefaultRequestOptions(@NonNull RequestOptionsFactory factory) {\n    this.defaultRequestOptionsFactory = Preconditions.checkNotNull(factory);\n    return this;\n  }\n\n  /**\n   * Sets the default {@link TransitionOptions} to use when starting a request that will load a\n   * resource with the given {@link Class}.\n   *\n   * <p>It's preferable but not required for the requested resource class to match the resource\n   * class applied here as long as the resource class applied here is assignable from the requested\n   * resource class. For example you can set a default transition for {@link\n   * android.graphics.drawable.Drawable} and that default transition will be used if you\n   * subsequently start requests for specific {@link android.graphics.drawable.Drawable} types like\n   * {@link com.bumptech.glide.load.resource.gif.GifDrawable} or {@link\n   * android.graphics.drawable.BitmapDrawable}. Specific types are always preferred so if you\n   * register a default transition for both {@link android.graphics.drawable.Drawable} and {@link\n   * android.graphics.drawable.BitmapDrawable} and then start a request for {@link\n   * android.graphics.drawable.BitmapDrawable}s, the transition you registered for {@link\n   * android.graphics.drawable.BitmapDrawable}s will be used.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  @NonNull\n  public <T> GlideBuilder setDefaultTransitionOptions(\n      @NonNull Class<T> clazz, @Nullable TransitionOptions<?, T> options) {\n    defaultTransitionOptions.put(clazz, options);\n    return this;\n  }\n\n  /**\n   * Sets the {@link MemorySizeCalculator} to use to calculate maximum sizes for default {@link\n   * MemoryCache MemoryCaches} and/or default {@link BitmapPool BitmapPools}.\n   *\n   * @see #setMemorySizeCalculator(MemorySizeCalculator)\n   * @param builder The builder to use (will not be modified).\n   * @return This builder.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  @NonNull\n  public GlideBuilder setMemorySizeCalculator(@NonNull MemorySizeCalculator.Builder builder) {\n    return setMemorySizeCalculator(builder.build());\n  }\n\n  /**\n   * Sets the {@link MemorySizeCalculator} to use to calculate maximum sizes for default {@link\n   * MemoryCache MemoryCaches} and/or default {@link BitmapPool BitmapPools}.\n   *\n   * <p>The given {@link MemorySizeCalculator} will not affect custom pools or caches provided via\n   * {@link #setBitmapPool(BitmapPool)} or {@link #setMemoryCache(MemoryCache)}.\n   *\n   * @param calculator The calculator to use.\n   * @return This builder.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @NonNull\n  public GlideBuilder setMemorySizeCalculator(@Nullable MemorySizeCalculator calculator) {\n    this.memorySizeCalculator = calculator;\n    return this;\n  }\n\n  /**\n   * Sets the {@link com.bumptech.glide.manager.ConnectivityMonitorFactory} to use to notify {@link\n   * com.bumptech.glide.RequestManager} of connectivity events. If not set {@link\n   * com.bumptech.glide.manager.DefaultConnectivityMonitorFactory} would be used.\n   *\n   * @param factory The factory to use\n   * @return This builder.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  @NonNull\n  public GlideBuilder setConnectivityMonitorFactory(@Nullable ConnectivityMonitorFactory factory) {\n    this.connectivityMonitorFactory = factory;\n    return this;\n  }\n\n  /**\n   * Sets a log level constant from those in {@link Log} to indicate the desired log verbosity.\n   *\n   * <p>The level must be one of {@link Log#VERBOSE}, {@link Log#DEBUG}, {@link Log#INFO}, {@link\n   * Log#WARN}, or {@link Log#ERROR}.\n   *\n   * <p>{@link Log#VERBOSE} means one or more lines will be logged per request, including timing\n   * logs and failures. {@link Log#DEBUG} means at most one line will be logged per successful\n   * request, including timing logs, although many lines may be logged for failures including\n   * multiple complete stack traces. {@link Log#INFO} means failed loads will be logged including\n   * multiple complete stack traces, but successful loads will not be logged at all. {@link\n   * Log#WARN} means only summaries of failed loads will be logged. {@link Log#ERROR} means only\n   * exceptional cases will be logged.\n   *\n   * <p>All logs will be logged using the 'Glide' tag.\n   *\n   * <p>Many other debugging logs are available in individual classes. The log level supplied here\n   * only controls a small set of informative and well formatted logs. Users wishing to debug\n   * certain aspects of the library can look for individual <code>TAG</code> variables at the tops\n   * of classes and use <code>adb shell setprop log.tag.TAG</code> to enable or disable any relevant\n   * tags.\n   *\n   * @param logLevel The log level to use from {@link Log}.\n   * @return This builder.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  @NonNull\n  public GlideBuilder setLogLevel(int logLevel) {\n    if (logLevel < Log.VERBOSE || logLevel > Log.ERROR) {\n      throw new IllegalArgumentException(\n          \"Log level must be one of Log.VERBOSE, Log.DEBUG,\" + \" Log.INFO, Log.WARN, or Log.ERROR\");\n    }\n    this.logLevel = logLevel;\n    return this;\n  }\n\n  /**\n   * If set to {@code true}, allows Glide to re-capture resources that are loaded into {@link\n   * com.bumptech.glide.request.target.Target}s which are subsequently de-referenced and garbage\n   * collected without being cleared.\n   *\n   * <p>Defaults to {@code false}.\n   *\n   * <p>Glide's resource re-use system is permissive, which means that's acceptable for callers to\n   * load resources into {@link com.bumptech.glide.request.target.Target}s and then never clear the\n   * {@link com.bumptech.glide.request.target.Target}. To do so, Glide uses {@link\n   * java.lang.ref.WeakReference}s to track resources that belong to {@link\n   * com.bumptech.glide.request.target.Target}s that haven't yet been cleared. Setting this method\n   * to {@code true} allows Glide to also maintain a hard reference to the underlying resource so\n   * that if the {@link com.bumptech.glide.request.target.Target} is garbage collected, Glide can\n   * return the underlying resource to it's memory cache so that subsequent requests will not\n   * unexpectedly re-load the resource from disk or source. As a side affect, it will take the\n   * system slightly longer to garbage collect the underlying resource because the weak reference\n   * has to be cleared and processed before the hard reference is removed. As a result, setting this\n   * method to {@code true} may transiently increase the memory usage of an application.\n   *\n   * <p>Leaving this method at the default {@code false} value will allow the platform to garbage\n   * collect resources more quickly, but will lead to unexpected memory cache misses if callers load\n   * resources into {@link com.bumptech.glide.request.target.Target}s but never clear them.\n   *\n   * <p>If you set this method to {@code true} you <em>must not</em> call {@link Bitmap#recycle()}\n   * or mutate any Bitmaps returned by Glide. If this method is set to {@code false}, recycling or\n   * mutating Bitmaps is inefficient but safe as long as you do not clear the corresponding {@link\n   * com.bumptech.glide.request.target.Target} used to load the {@link Bitmap}. However, if you set\n   * this method to {@code true} and recycle or mutate any returned {@link Bitmap}s or other mutable\n   * resources, Glide may recover those resources and attempt to use them later on, resulting in\n   * crashes, graphical corruption or undefined behavior.\n   *\n   * <p>Regardless of what value this method is set to, it's always good practice to clear {@link\n   * com.bumptech.glide.request.target.Target}s when you're done with the corresponding resource.\n   * Clearing {@link com.bumptech.glide.request.target.Target}s allows Glide to maximize resource\n   * re-use, minimize memory overhead and minimize unexpected behavior resulting from edge cases. If\n   * you use {@link RequestManager#clear(Target)}, calling {@link Bitmap#recycle()} or mutating\n   * {@link Bitmap}s is not only unsafe, it's also totally unnecessary and should be avoided. In all\n   * cases, prefer {@link RequestManager#clear(Target)} to {@link Bitmap#recycle()}.\n   *\n   * @return This builder.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  @NonNull\n  public GlideBuilder setIsActiveResourceRetentionAllowed(\n      boolean isActiveResourceRetentionAllowed) {\n    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;\n    return this;\n  }\n\n  /**\n   * Adds a global {@link RequestListener} that will be added to every request started with Glide.\n   *\n   * <p>Multiple {@link RequestListener}s can be added here, in {@link RequestManager} scopes or to\n   * individual {@link RequestBuilder}s. {@link RequestListener}s are called in the order they're\n   * added. Even if an earlier {@link RequestListener} returns {@code true} from {@link\n   * RequestListener#onLoadFailed(GlideException, Object, Target, boolean)} or {@link\n   * RequestListener#onResourceReady(Object, Object, Target, DataSource, boolean)}, it will not\n   * prevent subsequent {@link RequestListener}s from being called.\n   *\n   * <p>Because Glide requests can be started for any number of individual resource types, any\n   * listener added here has to accept any generic resource type in {@link\n   * RequestListener#onResourceReady(Object, Object, Target, DataSource, boolean)}. If you must base\n   * the behavior of the listener on the resource type, you will need to use {@code instanceof} to\n   * do so. It's not safe to cast resource types without first checking with {@code instanceof}.\n   */\n  @NonNull\n  public GlideBuilder addGlobalRequestListener(@NonNull RequestListener<Object> listener) {\n    if (defaultRequestListeners == null) {\n      defaultRequestListeners = new ArrayList<>();\n    }\n    defaultRequestListeners.add(listener);\n    return this;\n  }\n\n  /**\n   * Set to {@code true} to make Glide populate {@link\n   * com.bumptech.glide.load.engine.GlideException#setOrigin(Exception)} for failed requests.\n   *\n   * <p>The exception set by this method is not printed by {@link GlideException} and can only be\n   * viewed via a {@link RequestListener} that reads the field via {@link\n   * GlideException#getOrigin()}.\n   *\n   * <p>This is an experimental API that may be removed in the future.\n   */\n  public GlideBuilder setLogRequestOrigins(boolean isEnabled) {\n    glideExperimentsBuilder.update(new LogRequestOrigins(), isEnabled);\n    return this;\n  }\n\n  /**\n   * Set to {@code true} to make Glide use {@link android.graphics.ImageDecoder} when decoding\n   * {@link Bitmap}s on Android P and higher.\n   *\n   * <p>Calls to this method on versions of Android less than Q are ignored. Although ImageDecoder\n   * was added in Android O a bug prevents it from scaling images with exif orientations until Q.\n   * See b/136096254.\n   *\n   * <p>Specifically {@link android.graphics.ImageDecoder} will be used in place of {@link\n   * com.bumptech.glide.load.resource.bitmap.Downsampler} and {@link android.graphics.BitmapFactory}\n   * to decode {@link Bitmap}s. GIFs, resources, and all other types of {@link\n   * android.graphics.drawable.Drawable}s are not affected by this flag.\n   *\n   * <p>This flag is experimental and may be removed without deprecation in a future version.\n   *\n   * <p>When this flag is enabled, Bitmap's will not be re-used when decoding images, though they\n   * may still be used as part of {@link com.bumptech.glide.load.Transformation}s because {@link\n   * android.graphics.ImageDecoder} does not support Bitmap re-use.\n   *\n   * <p>When this flag is enabled {@link\n   * com.bumptech.glide.load.resource.bitmap.Downsampler#FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS} is\n   * ignored. All other {@link com.bumptech.glide.load.resource.bitmap.Downsampler} flags are\n   * obeyed, although there may be subtle behavior differences because many options are subject to\n   * the whims of {@link android.graphics.BitmapFactory} and {@link android.graphics.ImageDecoder}\n   * which may not agree.\n   */\n  public GlideBuilder setImageDecoderEnabledForBitmaps(boolean isEnabled) {\n    glideExperimentsBuilder.update(\n        new EnableImageDecoderForBitmaps(),\n        /* isEnabled= */ isEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);\n    return this;\n  }\n\n  /**\n   * Override the OS thread priority of threads created in {@link\n   * com.bumptech.glide.load.engine.executor.GlideExecutor#DefaultThreadFactory} with {@link\n   * com.bumptech.glide.load.engine.DecodeJob#GLIDE_THREAD_PRIORITY_OVERRIDE} Glide Option.\n   *\n   * <p>This is an experimental API that may be removed in the future.\n   */\n  public GlideBuilder setOverrideGlideThreadPriority(boolean isEnabled) {\n    glideExperimentsBuilder.update(new OverrideGlideThreadPriority(), isEnabled);\n    return this;\n  }\n\n  /**\n   * Set to {@code true} to make Glide use {@link\n   * android.provider.MediaStore#openAssetFileDescriptor(ContentResolver, Uri, String,\n   * CancellationSignal)} when opening {@link android.provider.MediaStore#AUTHORITY} content URIs\n   * when it is available.\n   *\n   * <p>This is an experimental API that may be removed in the future.\n   */\n  public GlideBuilder setUseMediaStoreOpenFileApisIfPossible(boolean isEnabled) {\n    glideExperimentsBuilder.update(new UseMediaStoreOpenFileApisIfPossible(), isEnabled);\n    return this;\n  }\n\n  /**\n   * Set to {@code true} to make Glide use {@link MemoryCategory} to set the memory category when\n   * the app is in the background.\n   *\n   * <p>This is an experimental API that may be removed in the future.\n   */\n  public GlideBuilder setMemoryCategoryInBackground(MemoryCategory memoryCategory) {\n    glideExperimentsBuilder.add(new MemoryCategoryInBackground(memoryCategory));\n    return this;\n  }\n\n  /**\n   * @deprecated This method does nothing. It will be hard coded and removed in a future release\n   *     without further warning.\n   */\n  @Deprecated\n  public GlideBuilder setPreserveGainmapAndColorSpaceForTransformations(boolean isEnabled) {\n    return this;\n  }\n\n  /**\n   * @deprecated This method does nothing. It will be hard coded and removed in a future release\n   *     without further warning.\n   */\n  @Deprecated\n  public GlideBuilder setEnableHardwareGainmapFixOnU(boolean isEnabled) {\n    return this;\n  }\n\n  /**\n   * @deprecated This method does nothing. It will be hard coded and removed in a future release\n   *     without further warning.\n   */\n  @Deprecated\n  public GlideBuilder setDisableHardwareBitmapsOnO(boolean disableHardwareBitmapsOnO) {\n    return this;\n  }\n\n  void setRequestManagerFactory(@Nullable RequestManagerFactory factory) {\n    this.requestManagerFactory = factory;\n  }\n\n  // For testing.\n  GlideBuilder setEngine(Engine engine) {\n    this.engine = engine;\n    return this;\n  }\n\n  @NonNull\n  Glide build(\n      @NonNull Context context,\n      List<GlideModule> manifestModules,\n      AppGlideModule annotationGeneratedGlideModule) {\n    if (sourceExecutor == null) {\n      sourceExecutor = GlideExecutor.newSourceExecutor();\n    }\n\n    if (diskCacheExecutor == null) {\n      diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();\n    }\n\n    if (animationExecutor == null) {\n      animationExecutor = GlideExecutor.newAnimationExecutor();\n    }\n\n    if (memorySizeCalculator == null) {\n      memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();\n    }\n\n    if (connectivityMonitorFactory == null) {\n      connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();\n    }\n\n    if (bitmapPool == null) {\n      int size = memorySizeCalculator.getBitmapPoolSize();\n      if (size > 0) {\n        bitmapPool = new LruBitmapPool(size);\n      } else {\n        bitmapPool = new BitmapPoolAdapter();\n      }\n    }\n\n    if (arrayPool == null) {\n      arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());\n    }\n\n    if (memoryCache == null) {\n      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());\n    }\n\n    if (diskCacheFactory == null) {\n      diskCacheFactory = new InternalCacheDiskCacheFactory(context);\n    }\n\n    if (engine == null) {\n      engine =\n          new Engine(\n              memoryCache,\n              diskCacheFactory,\n              diskCacheExecutor,\n              sourceExecutor,\n              GlideExecutor.newUnlimitedSourceExecutor(),\n              animationExecutor,\n              isActiveResourceRetentionAllowed);\n    }\n\n    if (defaultRequestListeners == null) {\n      defaultRequestListeners = Collections.emptyList();\n    } else {\n      defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);\n    }\n\n    GlideExperiments experiments = glideExperimentsBuilder.build();\n    RequestManagerRetriever requestManagerRetriever =\n        new RequestManagerRetriever(requestManagerFactory);\n\n    return new Glide(\n        context,\n        engine,\n        memoryCache,\n        bitmapPool,\n        arrayPool,\n        requestManagerRetriever,\n        connectivityMonitorFactory,\n        logLevel,\n        defaultRequestOptionsFactory,\n        defaultTransitionOptions,\n        defaultRequestListeners,\n        manifestModules,\n        annotationGeneratedGlideModule,\n        experiments);\n  }\n\n  static final class ManualOverrideHardwareBitmapMaxFdCount implements Experiment {\n\n    final int fdCount;\n\n    ManualOverrideHardwareBitmapMaxFdCount(int fdCount) {\n      this.fdCount = fdCount;\n    }\n  }\n\n  static final class EnableImageDecoderForBitmaps implements Experiment {}\n\n  /** See {@link #setLogRequestOrigins(boolean)}. */\n  public static final class LogRequestOrigins implements Experiment {}\n\n  /** See {@link #setOverrideGlideThreadPriority(boolean)}. */\n  public static final class OverrideGlideThreadPriority implements Experiment {}\n\n  /** See {@link #setUseMediaStoreOpenFileApisIfPossible(boolean)}. */\n  public static final class UseMediaStoreOpenFileApisIfPossible implements Experiment {}\n\n  /** See {@link #setMemoryCategoryInBackground(MemoryCategory)} */\n  public static final class MemoryCategoryInBackground implements Experiment {\n    private final MemoryCategory memoryCategory;\n\n    MemoryCategoryInBackground(MemoryCategory memoryCategory) {\n      this.memoryCategory = memoryCategory;\n    }\n\n    public MemoryCategory value() {\n      return memoryCategory;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/GlideContext.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.widget.ImageView;\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.Glide.RequestOptionsFactory;\nimport com.bumptech.glide.load.engine.Engine;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.ImageViewTargetFactory;\nimport com.bumptech.glide.request.target.ViewTarget;\nimport com.bumptech.glide.util.GlideSuppliers;\nimport com.bumptech.glide.util.GlideSuppliers.GlideSupplier;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\n/**\n * Global context for all loads in Glide containing and exposing the various registries and classes\n * required to load resources.\n */\n@SuppressWarnings(\"PMD.DataClass\")\npublic class GlideContext extends ContextWrapper {\n  @VisibleForTesting\n  static final TransitionOptions<?, ?> DEFAULT_TRANSITION_OPTIONS =\n      new GenericTransitionOptions<>();\n\n  private final ArrayPool arrayPool;\n  private final GlideSupplier<Registry> registry;\n  private final ImageViewTargetFactory imageViewTargetFactory;\n  private final RequestOptionsFactory defaultRequestOptionsFactory;\n  private final List<RequestListener<Object>> defaultRequestListeners;\n  private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions;\n  private final Engine engine;\n  private final GlideExperiments experiments;\n  private final int logLevel;\n\n  @Nullable\n  @GuardedBy(\"this\")\n  private RequestOptions defaultRequestOptions;\n\n  public GlideContext(\n      @NonNull Context context,\n      @NonNull ArrayPool arrayPool,\n      @NonNull GlideSupplier<Registry> registry,\n      @NonNull ImageViewTargetFactory imageViewTargetFactory,\n      @NonNull RequestOptionsFactory defaultRequestOptionsFactory,\n      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,\n      @NonNull List<RequestListener<Object>> defaultRequestListeners,\n      @NonNull Engine engine,\n      @NonNull GlideExperiments experiments,\n      int logLevel) {\n    super(context.getApplicationContext());\n    this.arrayPool = arrayPool;\n    this.imageViewTargetFactory = imageViewTargetFactory;\n    this.defaultRequestOptionsFactory = defaultRequestOptionsFactory;\n    this.defaultRequestListeners = defaultRequestListeners;\n    this.defaultTransitionOptions = defaultTransitionOptions;\n    this.engine = engine;\n    this.experiments = experiments;\n    this.logLevel = logLevel;\n\n    this.registry = GlideSuppliers.memorize(registry);\n  }\n\n  public List<RequestListener<Object>> getDefaultRequestListeners() {\n    return defaultRequestListeners;\n  }\n\n  public synchronized RequestOptions getDefaultRequestOptions() {\n    if (defaultRequestOptions == null) {\n      defaultRequestOptions = defaultRequestOptionsFactory.build().lock();\n    }\n\n    return defaultRequestOptions;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @NonNull\n  public <T> TransitionOptions<?, T> getDefaultTransitionOptions(@NonNull Class<T> transcodeClass) {\n    TransitionOptions<?, ?> result = defaultTransitionOptions.get(transcodeClass);\n    if (result == null) {\n      for (Entry<Class<?>, TransitionOptions<?, ?>> value : defaultTransitionOptions.entrySet()) {\n        if (value.getKey().isAssignableFrom(transcodeClass)) {\n          result = value.getValue();\n        }\n      }\n    }\n    if (result == null) {\n      result = DEFAULT_TRANSITION_OPTIONS;\n    }\n    return (TransitionOptions<?, T>) result;\n  }\n\n  @NonNull\n  public <X> ViewTarget<ImageView, X> buildImageViewTarget(\n      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {\n    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);\n  }\n\n  @NonNull\n  public Engine getEngine() {\n    return engine;\n  }\n\n  @NonNull\n  public Registry getRegistry() {\n    return registry.get();\n  }\n\n  public int getLogLevel() {\n    return logLevel;\n  }\n\n  @NonNull\n  public ArrayPool getArrayPool() {\n    return arrayPool;\n  }\n\n  public GlideExperiments getExperiments() {\n    return experiments;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/GlideExperiments.java",
    "content": "package com.bumptech.glide;\n\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Keeps track of a set of Experimental features that may be enabled in Glide, simplifying the\n * process of adding and removing them.\n *\n * <p>This is an experimental API, it may be removed at any point without deprecation or other\n * notice.\n */\n// non-final for mocking\npublic class GlideExperiments {\n\n  private final Map<Class<?>, Experiment> experiments;\n\n  @Synthetic\n  GlideExperiments(Builder builder) {\n    this.experiments =\n        Collections.unmodifiableMap(new HashMap<Class<?>, Experiment>(builder.experiments));\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Nullable\n  <T extends Experiment> T get(Class<T> clazz) {\n    return (T) experiments.get(clazz);\n  }\n\n  /**\n   * Returns {@code true} if the given experiment is enabled.\n   *\n   * <p>This is an experimental API, it may be removed at any point without deprecation or other\n   * notice.\n   */\n  public boolean isEnabled(Class<? extends Experiment> clazz) {\n    return experiments.containsKey(clazz);\n  }\n\n  interface Experiment {}\n\n  static final class Builder {\n    private final Map<Class<?>, Experiment> experiments = new HashMap<>();\n\n    Builder update(Experiment experiment, boolean isEnabled) {\n      if (isEnabled) {\n        add(experiment);\n      } else {\n        experiments.remove(experiment.getClass());\n      }\n      return this;\n    }\n\n    Builder add(Experiment experiment) {\n      experiments.put(experiment.getClass(), experiment);\n      return this;\n    }\n\n    GlideExperiments build() {\n      return new GlideExperiments(this);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/ListPreloader.java",
    "content": "package com.bumptech.glide;\n\nimport android.graphics.drawable.Drawable;\nimport android.widget.AbsListView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.util.List;\nimport java.util.Queue;\n\n/**\n * Loads a few resources ahead in the direction of scrolling in any {@link AbsListView} so that\n * images are in the memory cache just before the corresponding view in created in the list. Gives\n * the appearance of an infinitely large image cache, depending on scrolling speed, cpu speed, and\n * cache size.\n *\n * <p>Must be put using {@link\n * AbsListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}, or have its\n * corresponding methods called from another {@link android.widget.AbsListView.OnScrollListener} to\n * function.\n *\n * @param <T> The type of the model being displayed in the list.\n */\npublic class ListPreloader<T> implements AbsListView.OnScrollListener {\n  private final int maxPreload;\n  private final PreloadTargetQueue preloadTargetQueue;\n  private final RequestManager requestManager;\n  private final PreloadModelProvider<T> preloadModelProvider;\n  private final PreloadSizeProvider<T> preloadDimensionProvider;\n\n  private int lastEnd;\n  private int lastStart;\n  private int lastFirstVisible = -1;\n  private int totalItemCount;\n\n  private boolean isIncreasing = true;\n\n  /**\n   * An implementation of PreloadModelProvider should provide all the models that should be\n   * preloaded.\n   *\n   * @param <U> The type of the model being preloaded.\n   */\n  public interface PreloadModelProvider<U> {\n\n    /**\n     * Returns a {@link List} of models that need to be loaded for the list to display adapter items\n     * in positions between {@code start} and {@code end}.\n     *\n     * <p>{@code position} is the position in the view. If the view contains a mix of types (e.g.\n     * headers and images) then not every view position will actually have any model to return here.\n     * If that's the case for the given {@code position}, then return an empty list.\n     *\n     * <p>A list of any size can be returned so there can be multiple models per adapter position.\n     *\n     * <p>Every model returned by this method is expected to produce a valid {@link RequestBuilder}\n     * in {@link #getPreloadRequestBuilder(Object)}. If that's not possible for any set of models,\n     * avoid including them in the {@link List} returned by this method.\n     *\n     * <p>Although it's acceptable for the returned {@link List} to contain {@code null} models,\n     * it's best to filter them from the list instead of adding {@code null} to avoid unnecessary\n     * logic and expanding the size of the {@link List}\n     *\n     * @param position The adapter position.\n     */\n    @NonNull\n    List<U> getPreloadItems(int position);\n\n    /**\n     * Returns a {@link RequestBuilder} for a given item on which {@link\n     * RequestBuilder#load(Object)}} has been called or {@code null} if no valid load can be\n     * started.\n     *\n     * <p>For the preloader to be effective, the {@link RequestBuilder} returned here must use\n     * exactly the same size and set of options as the {@link RequestBuilder} used when the ``View``\n     * is bound. You may need to specify a size in both places to ensure that the width and height\n     * match exactly. If so, you can use {@link\n     * com.bumptech.glide.request.RequestOptions#override(int, int)} to do so.\n     *\n     * <p>The target and context will be provided by the preloader.\n     *\n     * <p>If {@link RequestBuilder#load(Object)} is not called by this method, the preloader will\n     * trigger a {@link RuntimeException}. If you don't want to load a particular item or position,\n     * filter it from the list returned by {@link #getPreloadItems(int)}.\n     *\n     * @param item The model to load.\n     */\n    @Nullable\n    RequestBuilder<?> getPreloadRequestBuilder(@NonNull U item);\n  }\n\n  /**\n   * An implementation of PreloadSizeProvider should provide the size of the view in the list where\n   * the resources will be displayed.\n   *\n   * @param <T> The type of the model the size should be provided for.\n   */\n  public interface PreloadSizeProvider<T> {\n\n    /**\n     * Returns the size of the view in the list where the resources will be displayed in pixels in\n     * the format [x, y], or {@code null} if no size is currently available.\n     *\n     * <p>Note - The dimensions returned here must precisely match those of the view in the list.\n     *\n     * <p>If this method returns {@code null}, then no request will be started for the given item.\n     *\n     * @param item A model\n     */\n    @Nullable\n    int[] getPreloadSize(@NonNull T item, int adapterPosition, int perItemPosition);\n  }\n\n  /**\n   * Constructor for {@link com.bumptech.glide.ListPreloader} that accepts interfaces for providing\n   * the dimensions of images to preload, the list of models to preload for a given position, and\n   * the request to use to load images.\n   *\n   * @param preloadModelProvider Provides models to load and requests capable of loading them.\n   * @param preloadDimensionProvider Provides the dimensions of images to load.\n   * @param maxPreload Maximum number of items to preload.\n   */\n  public ListPreloader(\n      @NonNull RequestManager requestManager,\n      @NonNull PreloadModelProvider<T> preloadModelProvider,\n      @NonNull PreloadSizeProvider<T> preloadDimensionProvider,\n      int maxPreload) {\n    this.requestManager = requestManager;\n    this.preloadModelProvider = preloadModelProvider;\n    this.preloadDimensionProvider = preloadDimensionProvider;\n    this.maxPreload = maxPreload;\n    preloadTargetQueue = new PreloadTargetQueue(maxPreload + 1);\n  }\n\n  @Override\n  public void onScrollStateChanged(AbsListView absListView, int scrollState) {\n    // Do nothing.\n  }\n\n  @Override\n  public void onScroll(\n      AbsListView absListView, int firstVisible, int visibleCount, int totalCount) {\n    if (totalItemCount == 0 && totalCount == 0) {\n      return;\n    }\n    totalItemCount = totalCount;\n    if (firstVisible > lastFirstVisible) {\n      preload(firstVisible + visibleCount, true);\n    } else if (firstVisible < lastFirstVisible) {\n      preload(firstVisible, false);\n    }\n    lastFirstVisible = firstVisible;\n  }\n\n  private void preload(int start, boolean increasing) {\n    if (isIncreasing != increasing) {\n      isIncreasing = increasing;\n      cancelAll();\n    }\n    preload(start, start + (increasing ? maxPreload : -maxPreload));\n  }\n\n  private void preload(int from, int to) {\n    int start;\n    int end;\n    if (from < to) {\n      start = Math.max(lastEnd, from);\n      end = to;\n    } else {\n      start = to;\n      end = Math.min(lastStart, from);\n    }\n    end = Math.min(totalItemCount, end);\n    start = Math.min(totalItemCount, Math.max(0, start));\n\n    if (from < to) {\n      // Increasing\n      for (int i = start; i < end; i++) {\n        preloadAdapterPosition(\n            preloadModelProvider.getPreloadItems(i), /* position= */ i, /* isIncreasing= */ true);\n      }\n    } else {\n      // Decreasing\n      for (int i = end - 1; i >= start; i--) {\n        preloadAdapterPosition(\n            preloadModelProvider.getPreloadItems(i), /* position= */ i, /* isIncreasing= */ false);\n      }\n    }\n\n    lastStart = start;\n    lastEnd = end;\n  }\n\n  private void preloadAdapterPosition(List<T> items, int position, boolean isIncreasing) {\n    final int numItems = items.size();\n    if (isIncreasing) {\n      for (int i = 0; i < numItems; ++i) {\n        preloadItem(items.get(i), position, i);\n      }\n    } else {\n      for (int i = numItems - 1; i >= 0; --i) {\n        preloadItem(items.get(i), position, i);\n      }\n    }\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void preloadItem(@Nullable T item, int position, int perItemPosition) {\n    if (item == null) {\n      return;\n    }\n    int[] dimensions = preloadDimensionProvider.getPreloadSize(item, position, perItemPosition);\n    if (dimensions == null) {\n      return;\n    }\n    RequestBuilder<Object> preloadRequestBuilder =\n        (RequestBuilder<Object>) preloadModelProvider.getPreloadRequestBuilder(item);\n    if (preloadRequestBuilder == null) {\n      return;\n    }\n\n    preloadRequestBuilder.into(preloadTargetQueue.next(dimensions[0], dimensions[1]));\n  }\n\n  private void cancelAll() {\n    for (int i = 0; i < preloadTargetQueue.queue.size(); i++) {\n      requestManager.clear(preloadTargetQueue.next(0, 0));\n    }\n  }\n\n  private static final class PreloadTargetQueue {\n    @Synthetic final Queue<PreloadTarget> queue;\n\n    // The loop is short and the only point is to create the objects.\n    @SuppressWarnings(\"PMD.AvoidInstantiatingObjectsInLoops\")\n    PreloadTargetQueue(int size) {\n      queue = Util.createQueue(size);\n\n      for (int i = 0; i < size; i++) {\n        queue.offer(new PreloadTarget());\n      }\n    }\n\n    public PreloadTarget next(int width, int height) {\n      final PreloadTarget result = queue.poll();\n      queue.offer(result);\n      result.photoWidth = width;\n      result.photoHeight = height;\n      return result;\n    }\n  }\n\n  private static final class PreloadTarget implements Target<Object> {\n    @Synthetic int photoHeight;\n    @Synthetic int photoWidth;\n    @Nullable private Request request;\n\n    @Synthetic\n    PreloadTarget() {}\n\n    @Override\n    public void onLoadStarted(@Nullable Drawable placeholder) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onLoadFailed(@Nullable Drawable errorDrawable) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onResourceReady(\n        @NonNull Object resource, @Nullable Transition<? super Object> transition) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onLoadCleared(@Nullable Drawable placeholder) {\n      // Do nothing.\n    }\n\n    @Override\n    public void getSize(@NonNull SizeReadyCallback cb) {\n      cb.onSizeReady(photoWidth, photoHeight);\n    }\n\n    @Override\n    public void removeCallback(@NonNull SizeReadyCallback cb) {\n      // Do nothing because we don't retain references to SizeReadyCallbacks.\n    }\n\n    @Override\n    public void setRequest(@Nullable Request request) {\n      this.request = request;\n    }\n\n    @Nullable\n    @Override\n    public Request getRequest() {\n      return request;\n    }\n\n    @Override\n    public void onStart() {\n      // Do nothing.\n    }\n\n    @Override\n    public void onStop() {\n      // Do nothing.\n    }\n\n    @Override\n    public void onDestroy() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/MemoryCategory.java",
    "content": "package com.bumptech.glide;\n\n/** An enum for dynamically modifying the amount of memory Glide is able to use. */\npublic enum MemoryCategory {\n  /** Tells Glide's memory cache and bitmap pool to use no memory. */\n  ZERO(0f),\n  /**\n   * Tells Glide's memory cache and bitmap pool to use at most half of their initial maximum size.\n   */\n  LOW(0.5f),\n  /** Tells Glide's memory cache and bitmap pool to use at most their initial maximum size. */\n  NORMAL(1f),\n  /**\n   * Tells Glide's memory cache and bitmap pool to use at most one and a half times their initial\n   * maximum size.\n   */\n  HIGH(1.5f);\n\n  private final float multiplier;\n\n  MemoryCategory(float multiplier) {\n    this.multiplier = multiplier;\n  }\n\n  /**\n   * Returns the multiplier that should be applied to the initial maximum size of Glide's memory\n   * cache and bitmap pool.\n   */\n  public float getMultiplier() {\n    return multiplier;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/ModelTypes.java",
    "content": "package com.bumptech.glide;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport java.io.File;\nimport java.net.URL;\n\n/**\n * Ensures that the set of explicitly supported model types remains consistent across Glide's API\n * surface.\n */\ninterface ModelTypes<T> {\n  @NonNull\n  @CheckResult\n  T load(@Nullable Bitmap bitmap);\n\n  @NonNull\n  @CheckResult\n  T load(@Nullable Drawable drawable);\n\n  @NonNull\n  @CheckResult\n  T load(@Nullable String string);\n\n  @NonNull\n  @CheckResult\n  T load(@Nullable Uri uri);\n\n  @NonNull\n  @CheckResult\n  T load(@Nullable File file);\n\n  @NonNull\n  @CheckResult\n  T load(@RawRes @DrawableRes @Nullable Integer resourceId);\n\n  @Deprecated\n  @CheckResult\n  T load(@Nullable URL url);\n\n  @NonNull\n  @CheckResult\n  T load(@Nullable byte[] model);\n\n  @NonNull\n  @CheckResult\n  @SuppressWarnings(\"unchecked\")\n  T load(@Nullable Object model);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/Priority.java",
    "content": "package com.bumptech.glide;\n\n/**\n * Priorities for completing loads. If more than one load is queued at a time, the load with the\n * higher priority will be started first. Priorities are considered best effort, there are no\n * guarantees about the order in which loads will start or finish.\n */\npublic enum Priority {\n  IMMEDIATE,\n  HIGH,\n  NORMAL,\n  LOW,\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/Registry.java",
    "content": "package com.bumptech.glide;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.util.Pools.Pool;\nimport com.bumptech.glide.load.Encoder;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.load.data.DataRewinder;\nimport com.bumptech.glide.load.data.DataRewinderRegistry;\nimport com.bumptech.glide.load.engine.DecodePath;\nimport com.bumptech.glide.load.engine.LoadPath;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.ModelLoaderRegistry;\nimport com.bumptech.glide.load.resource.transcode.ResourceTranscoder;\nimport com.bumptech.glide.load.resource.transcode.TranscoderRegistry;\nimport com.bumptech.glide.provider.EncoderRegistry;\nimport com.bumptech.glide.provider.ImageHeaderParserRegistry;\nimport com.bumptech.glide.provider.LoadPathCache;\nimport com.bumptech.glide.provider.ModelToResourceClassCache;\nimport com.bumptech.glide.provider.ResourceDecoderRegistry;\nimport com.bumptech.glide.provider.ResourceEncoderRegistry;\nimport com.bumptech.glide.util.pool.FactoryPools;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Manages component registration to extend or replace Glide's default loading, decoding, and\n * encoding logic.\n */\n// Public API.\n@SuppressWarnings({\"WeakerAccess\", \"unused\"})\npublic class Registry {\n  public static final String BUCKET_ANIMATION = \"Animation\";\n\n  /**\n   * @deprecated Identical to {@link #BUCKET_ANIMATION}, just with a more confusing name. This\n   *     bucket can be used for all animation types (including webp).\n   */\n  @Deprecated public static final String BUCKET_GIF = BUCKET_ANIMATION;\n\n  public static final String BUCKET_BITMAP = \"Bitmap\";\n  public static final String BUCKET_BITMAP_DRAWABLE = \"BitmapDrawable\";\n  private static final String BUCKET_PREPEND_ALL = \"legacy_prepend_all\";\n  private static final String BUCKET_APPEND_ALL = \"legacy_append\";\n\n  private final ModelLoaderRegistry modelLoaderRegistry;\n  private final EncoderRegistry encoderRegistry;\n  private final ResourceDecoderRegistry decoderRegistry;\n  private final ResourceEncoderRegistry resourceEncoderRegistry;\n  private final DataRewinderRegistry dataRewinderRegistry;\n  private final TranscoderRegistry transcoderRegistry;\n  private final ImageHeaderParserRegistry imageHeaderParserRegistry;\n\n  private final ModelToResourceClassCache modelToResourceClassCache =\n      new ModelToResourceClassCache();\n  private final LoadPathCache loadPathCache = new LoadPathCache();\n  private final Pool<List<Throwable>> throwableListPool = FactoryPools.threadSafeList();\n\n  public Registry() {\n    this.modelLoaderRegistry = new ModelLoaderRegistry(throwableListPool);\n    this.encoderRegistry = new EncoderRegistry();\n    this.decoderRegistry = new ResourceDecoderRegistry();\n    this.resourceEncoderRegistry = new ResourceEncoderRegistry();\n    this.dataRewinderRegistry = new DataRewinderRegistry();\n    this.transcoderRegistry = new TranscoderRegistry();\n    this.imageHeaderParserRegistry = new ImageHeaderParserRegistry();\n    setResourceDecoderBucketPriorityList(\n        Arrays.asList(BUCKET_ANIMATION, BUCKET_BITMAP, BUCKET_BITMAP_DRAWABLE));\n  }\n\n  /**\n   * Registers the given {@link Encoder} for the given data class (InputStream, FileDescriptor etc).\n   *\n   * <p>The {@link Encoder} will be used both for the exact data class and any subtypes. For\n   * example, registering an {@link Encoder} for {@link java.io.InputStream} will result in the\n   * {@link Encoder} being used for {@link\n   * android.content.res.AssetFileDescriptor.AutoCloseInputStream}, {@link java.io.FileInputStream}\n   * and any other subclass.\n   *\n   * <p>If multiple {@link Encoder}s are registered for the same type or super type, the {@link\n   * Encoder} that is registered first will be used.\n   *\n   * @deprecated Use the equivalent {@link #append(Class, Class, ModelLoaderFactory)} method\n   *     instead.\n   */\n  @NonNull\n  @Deprecated\n  public <Data> Registry register(@NonNull Class<Data> dataClass, @NonNull Encoder<Data> encoder) {\n    return append(dataClass, encoder);\n  }\n\n  /**\n   * Appends the given {@link Encoder} onto the list of available {@link Encoder}s so that it is\n   * attempted after all earlier and default {@link Encoder}s for the given data class.\n   *\n   * <p>The {@link Encoder} will be used both for the exact data class and any subtypes. For\n   * example, registering an {@link Encoder} for {@link java.io.InputStream} will result in the\n   * {@link Encoder} being used for {@link\n   * android.content.res.AssetFileDescriptor.AutoCloseInputStream}, {@link java.io.FileInputStream}\n   * and any other subclass.\n   *\n   * <p>If multiple {@link Encoder}s are registered for the same type or super type, the {@link\n   * Encoder} that is registered first will be used.\n   *\n   * @see #prepend(Class, Encoder)\n   */\n  @NonNull\n  public <Data> Registry append(@NonNull Class<Data> dataClass, @NonNull Encoder<Data> encoder) {\n    encoderRegistry.append(dataClass, encoder);\n    return this;\n  }\n\n  /**\n   * Prepends the given {@link Encoder} into the list of available {@link Encoder}s so that it is\n   * attempted before all later and default {@link Encoder}s for the given data class.\n   *\n   * <p>This method allows you to replace the default {@link Encoder} because it ensures the\n   * registered {@link Encoder} will run first. If multiple {@link Encoder}s are registered for the\n   * same type or super type, the {@link Encoder} that is registered first will be used.\n   *\n   * @see #append(Class, Encoder)\n   */\n  @NonNull\n  public <Data> Registry prepend(@NonNull Class<Data> dataClass, @NonNull Encoder<Data> encoder) {\n    encoderRegistry.prepend(dataClass, encoder);\n    return this;\n  }\n\n  /**\n   * Appends the given {@link ResourceDecoder} onto the list of all available {@link\n   * ResourceDecoder}s allowing it to be used if all earlier and default {@link ResourceDecoder}s\n   * for the given types fail (or there are none).\n   *\n   * <p>If you're attempting to replace an existing {@link ResourceDecoder} or would like to ensure\n   * that your {@link ResourceDecoder} gets the chance to run before an existing {@link\n   * ResourceDecoder}, use {@link #prepend(Class, Class, ResourceDecoder)}. This method is best for\n   * new types of resources and data or as a way to add an additional fallback decoder for an\n   * existing type of data.\n   *\n   * @see #append(String, Class, Class, ResourceDecoder)\n   * @see #prepend(Class, Class, ResourceDecoder)\n   * @param dataClass The data that will be decoded from ({@link java.io.InputStream}, {@link\n   *     java.io.FileDescriptor} etc).\n   * @param resourceClass The resource that will be decoded to ({@link android.graphics.Bitmap},\n   *     {@link com.bumptech.glide.load.resource.gif.GifDrawable} etc).\n   * @param decoder The {@link ResourceDecoder} to register.\n   */\n  @NonNull\n  public <Data, TResource> Registry append(\n      @NonNull Class<Data> dataClass,\n      @NonNull Class<TResource> resourceClass,\n      @NonNull ResourceDecoder<Data, TResource> decoder) {\n    append(BUCKET_APPEND_ALL, dataClass, resourceClass, decoder);\n    return this;\n  }\n\n  /**\n   * Appends the given {@link ResourceDecoder} onto the list of available {@link ResourceDecoder}s\n   * in this bucket, allowing it to be used if all earlier and default {@link ResourceDecoder}s for\n   * the given types in this bucket fail (or there are none).\n   *\n   * <p>If you're attempting to replace an existing {@link ResourceDecoder} or would like to ensure\n   * that your {@link ResourceDecoder} gets the chance to run before an existing {@link\n   * ResourceDecoder}, use {@link #prepend(Class, Class, ResourceDecoder)}. This method is best for\n   * new types of resources and data or as a way to add an additional fallback decoder for an\n   * existing type of data.\n   *\n   * @see #prepend(String, Class, Class, ResourceDecoder)\n   * @see #setResourceDecoderBucketPriorityList(List)\n   * @param bucket The bucket identifier to add this decoder to.\n   * @param dataClass The data that will be decoded from ({@link java.io.InputStream}, {@link\n   *     java.io.FileDescriptor} etc).\n   * @param resourceClass The resource that will be decoded to ({@link android.graphics.Bitmap},\n   *     {@link com.bumptech.glide.load.resource.gif.GifDrawable} etc).\n   * @param decoder The {@link ResourceDecoder} to register.\n   */\n  @NonNull\n  public <Data, TResource> Registry append(\n      @NonNull String bucket,\n      @NonNull Class<Data> dataClass,\n      @NonNull Class<TResource> resourceClass,\n      @NonNull ResourceDecoder<Data, TResource> decoder) {\n    decoderRegistry.append(bucket, decoder, dataClass, resourceClass);\n    return this;\n  }\n\n  /**\n   * Prepends the given {@link ResourceDecoder} into the list of all available {@link\n   * ResourceDecoder}s so that it is attempted before all later and default {@link ResourceDecoder}s\n   * for the given types.\n   *\n   * <p>This method allows you to replace the default {@link ResourceDecoder} because it ensures the\n   * registered {@link ResourceDecoder} will run first. You can use the {@link\n   * ResourceDecoder#handles(Object, Options)} to fall back to the default {@link ResourceDecoder}s\n   * if you only want to change the default functionality for certain types of data.\n   *\n   * @see #prepend(String, Class, Class, ResourceDecoder)\n   * @see #append(Class, Class, ResourceDecoder)\n   * @param dataClass The data that will be decoded from ({@link java.io.InputStream}, {@link\n   *     java.io.FileDescriptor} etc).\n   * @param resourceClass The resource that will be decoded to ({@link android.graphics.Bitmap},\n   *     {@link com.bumptech.glide.load.resource.gif.GifDrawable} etc).\n   * @param decoder The {@link ResourceDecoder} to register.\n   */\n  @NonNull\n  public <Data, TResource> Registry prepend(\n      @NonNull Class<Data> dataClass,\n      @NonNull Class<TResource> resourceClass,\n      @NonNull ResourceDecoder<Data, TResource> decoder) {\n    prepend(BUCKET_PREPEND_ALL, dataClass, resourceClass, decoder);\n    return this;\n  }\n\n  /**\n   * Prepends the given {@link ResourceDecoder} into the list of available {@link ResourceDecoder}s\n   * in the same bucket so that it is attempted before all later and default {@link\n   * ResourceDecoder}s for the given types in that bucket.\n   *\n   * <p>This method allows you to replace the default {@link ResourceDecoder} for this bucket\n   * because it ensures the registered {@link ResourceDecoder} will run first. You can use the\n   * {@link ResourceDecoder#handles(Object, Options)} to fall back to the default {@link\n   * ResourceDecoder}s if you only want to change the default functionality for certain types of\n   * data.\n   *\n   * @see #append(String, Class, Class, ResourceDecoder)\n   * @see #setResourceDecoderBucketPriorityList(List)\n   * @param bucket The bucket identifier to add this decoder to.\n   * @param dataClass The data that will be decoded from ({@link java.io.InputStream}, {@link\n   *     java.io.FileDescriptor} etc).\n   * @param resourceClass The resource that will be decoded to ({@link android.graphics.Bitmap},\n   *     {@link com.bumptech.glide.load.resource.gif.GifDrawable} etc).\n   * @param decoder The {@link ResourceDecoder} to register.\n   */\n  @NonNull\n  public <Data, TResource> Registry prepend(\n      @NonNull String bucket,\n      @NonNull Class<Data> dataClass,\n      @NonNull Class<TResource> resourceClass,\n      @NonNull ResourceDecoder<Data, TResource> decoder) {\n    decoderRegistry.prepend(bucket, decoder, dataClass, resourceClass);\n    return this;\n  }\n\n  /**\n   * Overrides the default ordering of resource decoder buckets. You may also add custom buckets\n   * which are identified as a unique string. Glide will attempt to decode using decoders in the\n   * highest priority bucket before moving on to the next one.\n   *\n   * <p>The default order is [{@link #BUCKET_ANIMATION}, {@link #BUCKET_BITMAP}, {@link\n   * #BUCKET_BITMAP_DRAWABLE}].\n   *\n   * <p>When registering decoders, you can use these buckets to specify the ordering relative only\n   * to other decoders in that bucket.\n   *\n   * @see #append(String, Class, Class, ResourceDecoder)\n   * @see #prepend(String, Class, Class, ResourceDecoder)\n   * @param buckets The list of bucket identifiers in order from highest priority to least priority.\n   */\n  // Final to avoid a PMD error.\n  @NonNull\n  public final Registry setResourceDecoderBucketPriorityList(@NonNull List<String> buckets) {\n    // See #3296 and https://bugs.openjdk.java.net/browse/JDK-6260652.\n    List<String> modifiedBuckets = new ArrayList<>(buckets.size());\n    modifiedBuckets.add(BUCKET_PREPEND_ALL);\n    // See https://github.com/bumptech/glide/issues/4309.\n    for (String bucket : buckets) {\n      modifiedBuckets.add(bucket);\n    }\n    modifiedBuckets.add(BUCKET_APPEND_ALL);\n    decoderRegistry.setBucketPriorityList(modifiedBuckets);\n    return this;\n  }\n\n  /**\n   * Appends the given {@link ResourceEncoder} into the list of available {@link ResourceEncoder}s\n   * so that it is attempted after all earlier and default {@link ResourceEncoder}s for the given\n   * data type.\n   *\n   * <p>The {@link ResourceEncoder} will be used both for the exact resource class and any subtypes.\n   * For example, registering an {@link ResourceEncoder} for {@link\n   * android.graphics.drawable.Drawable} (not recommended) will result in the {@link\n   * ResourceEncoder} being used for {@link android.graphics.drawable.BitmapDrawable} and {@link\n   * com.bumptech.glide.load.resource.gif.GifDrawable} and any other subclass.\n   *\n   * <p>If multiple {@link ResourceEncoder}s are registered for the same type or super type, the\n   * {@link ResourceEncoder} that is registered first will be used.\n   *\n   * @deprecated Use the equivalent {@link #append(Class, ResourceEncoder)} method instead.\n   */\n  @NonNull\n  @Deprecated\n  public <TResource> Registry register(\n      @NonNull Class<TResource> resourceClass, @NonNull ResourceEncoder<TResource> encoder) {\n    return append(resourceClass, encoder);\n  }\n\n  /**\n   * Appends the given {@link ResourceEncoder} into the list of available {@link ResourceEncoder}s\n   * so that it is attempted after all earlier and default {@link ResourceEncoder}s for the given\n   * data type.\n   *\n   * <p>The {@link ResourceEncoder} will be used both for the exact resource class and any subtypes.\n   * For example, registering an {@link ResourceEncoder} for {@link\n   * android.graphics.drawable.Drawable} (not recommended) will result in the {@link\n   * ResourceEncoder} being used for {@link android.graphics.drawable.BitmapDrawable} and {@link\n   * com.bumptech.glide.load.resource.gif.GifDrawable} and any other subclass.\n   *\n   * <p>If multiple {@link ResourceEncoder}s are registered for the same type or super type, the\n   * {@link ResourceEncoder} that is registered first will be used.\n   *\n   * @see #prepend(Class, ResourceEncoder)\n   */\n  @NonNull\n  public <TResource> Registry append(\n      @NonNull Class<TResource> resourceClass, @NonNull ResourceEncoder<TResource> encoder) {\n    resourceEncoderRegistry.append(resourceClass, encoder);\n    return this;\n  }\n\n  /**\n   * Prepends the given {@link ResourceEncoder} into the list of available {@link ResourceEncoder}s\n   * so that it is attempted before all later and default {@link ResourceEncoder}s for the given\n   * data type.\n   *\n   * <p>This method allows you to replace the default {@link ResourceEncoder} because it ensures the\n   * registered {@link ResourceEncoder} will run first. If multiple {@link ResourceEncoder}s are\n   * registered for the same type or super type, the {@link ResourceEncoder} that is registered\n   * first will be used.\n   *\n   * @see #append(Class, ResourceEncoder)\n   */\n  @NonNull\n  public <TResource> Registry prepend(\n      @NonNull Class<TResource> resourceClass, @NonNull ResourceEncoder<TResource> encoder) {\n    resourceEncoderRegistry.prepend(resourceClass, encoder);\n    return this;\n  }\n\n  /**\n   * Registers a new {@link com.bumptech.glide.load.data.DataRewinder.Factory} to handle a\n   * non-default data type that can be rewind to allow for efficient reads of file headers.\n   */\n  @NonNull\n  public Registry register(@NonNull DataRewinder.Factory<?> factory) {\n    dataRewinderRegistry.register(factory);\n    return this;\n  }\n\n  /**\n   * Registers the given {@link ResourceTranscoder} to convert from the given resource {@link Class}\n   * to the given transcode {@link Class}.\n   *\n   * @param resourceClass The class that will be transcoded from (e.g. {@link\n   *     android.graphics.Bitmap}).\n   * @param transcodeClass The class that will be transcoded to (e.g. {@link\n   *     android.graphics.drawable.BitmapDrawable}).\n   * @param transcoder The {@link ResourceTranscoder} to register.\n   */\n  @NonNull\n  public <TResource, Transcode> Registry register(\n      @NonNull Class<TResource> resourceClass,\n      @NonNull Class<Transcode> transcodeClass,\n      @NonNull ResourceTranscoder<TResource, Transcode> transcoder) {\n    transcoderRegistry.register(resourceClass, transcodeClass, transcoder);\n    return this;\n  }\n\n  /**\n   * Registers a new {@link ImageHeaderParser} that can obtain some basic metadata from an image\n   * header (orientation, type etc).\n   */\n  @NonNull\n  public Registry register(@NonNull ImageHeaderParser parser) {\n    imageHeaderParserRegistry.add(parser);\n    return this;\n  }\n\n  /**\n   * Appends a new {@link ModelLoaderFactory} onto the end of the existing set so that the\n   * constructed {@link ModelLoader} will be tried after all default and previously registered\n   * {@link ModelLoader}s for the given model and data classes.\n   *\n   * <p>If you're attempting to replace an existing {@link ModelLoader}, use {@link #prepend(Class,\n   * Class, ModelLoaderFactory)}. This method is best for new types of models and/or data or as a\n   * way to add an additional fallback loader for an existing type of model/data.\n   *\n   * <p>If multiple {@link ModelLoaderFactory}s are registered for the same model and/or data\n   * classes, the {@link ModelLoader}s they produce will be attempted in the order the {@link\n   * ModelLoaderFactory}s were registered. Only if all {@link ModelLoader}s fail will the entire\n   * request fail.\n   *\n   * @see #prepend(Class, Class, ModelLoaderFactory)\n   * @see #replace(Class, Class, ModelLoaderFactory)\n   * @param modelClass The model class (e.g. URL, file path).\n   * @param dataClass the data class (e.g. {@link java.io.InputStream}, {@link\n   *     java.io.FileDescriptor}).\n   */\n  @NonNull\n  public <Model, Data> Registry append(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<Model, Data> factory) {\n    modelLoaderRegistry.append(modelClass, dataClass, factory);\n    return this;\n  }\n\n  /**\n   * Prepends a new {@link ModelLoaderFactory} onto the beginning of the existing set so that the\n   * constructed {@link ModelLoader} will be tried before all default and previously registered\n   * {@link ModelLoader}s for the given model and data classes.\n   *\n   * <p>If you're attempting to add additional functionality or add a backup that should run only\n   * after the default {@link ModelLoader}s run, use {@link #append(Class, Class,\n   * ModelLoaderFactory)}. This method is best for adding an additional case to Glide's existing\n   * functionality that should run first. This method will still run Glide's default {@link\n   * ModelLoader}s if the prepended {@link ModelLoader}s fail.\n   *\n   * <p>If multiple {@link ModelLoaderFactory}s are registered for the same model and/or data\n   * classes, the {@link ModelLoader}s they produce will be attempted in the order the {@link\n   * ModelLoaderFactory}s were registered. Only if all {@link ModelLoader}s fail will the entire\n   * request fail.\n   *\n   * @see #append(Class, Class, ModelLoaderFactory)\n   * @see #replace(Class, Class, ModelLoaderFactory)\n   * @param modelClass The model class (e.g. URL, file path).\n   * @param dataClass the data class (e.g. {@link java.io.InputStream}, {@link\n   *     java.io.FileDescriptor}).\n   */\n  @NonNull\n  public <Model, Data> Registry prepend(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<Model, Data> factory) {\n    modelLoaderRegistry.prepend(modelClass, dataClass, factory);\n    return this;\n  }\n\n  /**\n   * Removes all default and previously registered {@link ModelLoaderFactory}s for the given data\n   * and model class and replaces all of them with the single {@link ModelLoader} provided.\n   *\n   * <p>If you're attempting to add additional functionality or add a backup that should run only\n   * after the default {@link ModelLoader}s run, use {@link #append(Class, Class,\n   * ModelLoaderFactory)}. This method should be used only when you want to ensure that Glide's\n   * default {@link ModelLoader}s do not run.\n   *\n   * <p>One good use case for this method is when you want to replace Glide's default networking\n   * library with your OkHttp, Volley, or your own implementation. Using {@link #prepend(Class,\n   * Class, ModelLoaderFactory)} or {@link #append(Class, Class, ModelLoaderFactory)} may still\n   * allow Glide's default networking library to run in some cases. Using this method will ensure\n   * that only your networking library will run and that the request will fail otherwise.\n   *\n   * @see #prepend(Class, Class, ModelLoaderFactory)\n   * @see #append(Class, Class, ModelLoaderFactory)\n   * @param modelClass The model class (e.g. URL, file path).\n   * @param dataClass the data class (e.g. {@link java.io.InputStream}, {@link\n   *     java.io.FileDescriptor}).\n   */\n  @NonNull\n  public <Model, Data> Registry replace(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {\n    modelLoaderRegistry.replace(modelClass, dataClass, factory);\n    return this;\n  }\n\n  @Nullable\n  public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(\n      @NonNull Class<Data> dataClass,\n      @NonNull Class<TResource> resourceClass,\n      @NonNull Class<Transcode> transcodeClass) {\n    LoadPath<Data, TResource, Transcode> result =\n        loadPathCache.get(dataClass, resourceClass, transcodeClass);\n    if (loadPathCache.isEmptyLoadPath(result)) {\n      return null;\n    } else if (result == null) {\n      List<DecodePath<Data, TResource, Transcode>> decodePaths =\n          getDecodePaths(dataClass, resourceClass, transcodeClass);\n      // It's possible there is no way to decode or transcode to the desired types from a given\n      // data class.\n      if (decodePaths.isEmpty()) {\n        result = null;\n      } else {\n        result =\n            new LoadPath<>(\n                dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);\n      }\n      loadPathCache.put(dataClass, resourceClass, transcodeClass, result);\n    }\n    return result;\n  }\n\n  @NonNull\n  private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(\n      @NonNull Class<Data> dataClass,\n      @NonNull Class<TResource> resourceClass,\n      @NonNull Class<Transcode> transcodeClass) {\n    List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();\n    List<Class<TResource>> registeredResourceClasses =\n        decoderRegistry.getResourceClasses(dataClass, resourceClass);\n\n    for (Class<TResource> registeredResourceClass : registeredResourceClasses) {\n      List<Class<Transcode>> registeredTranscodeClasses =\n          transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);\n\n      for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {\n\n        List<ResourceDecoder<Data, TResource>> decoders =\n            decoderRegistry.getDecoders(dataClass, registeredResourceClass);\n        ResourceTranscoder<TResource, Transcode> transcoder =\n            transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);\n        @SuppressWarnings(\"PMD.AvoidInstantiatingObjectsInLoops\")\n        DecodePath<Data, TResource, Transcode> path =\n            new DecodePath<>(\n                dataClass,\n                registeredResourceClass,\n                registeredTranscodeClass,\n                decoders,\n                transcoder,\n                throwableListPool);\n        decodePaths.add(path);\n      }\n    }\n    return decodePaths;\n  }\n\n  @NonNull\n  public <Model, TResource, Transcode> List<Class<?>> getRegisteredResourceClasses(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<TResource> resourceClass,\n      @NonNull Class<Transcode> transcodeClass) {\n    List<Class<?>> result =\n        modelToResourceClassCache.get(modelClass, resourceClass, transcodeClass);\n\n    if (result == null) {\n      result = new ArrayList<>();\n      List<Class<?>> dataClasses = modelLoaderRegistry.getDataClasses(modelClass);\n      for (Class<?> dataClass : dataClasses) {\n        List<? extends Class<?>> registeredResourceClasses =\n            decoderRegistry.getResourceClasses(dataClass, resourceClass);\n        for (Class<?> registeredResourceClass : registeredResourceClasses) {\n          List<Class<Transcode>> registeredTranscodeClasses =\n              transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);\n          if (!registeredTranscodeClasses.isEmpty() && !result.contains(registeredResourceClass)) {\n            result.add(registeredResourceClass);\n          }\n        }\n      }\n      modelToResourceClassCache.put(\n          modelClass, resourceClass, transcodeClass, Collections.unmodifiableList(result));\n    }\n\n    return result;\n  }\n\n  public boolean isResourceEncoderAvailable(@NonNull Resource<?> resource) {\n    return resourceEncoderRegistry.get(resource.getResourceClass()) != null;\n  }\n\n  @NonNull\n  public <X> ResourceEncoder<X> getResultEncoder(@NonNull Resource<X> resource)\n      throws NoResultEncoderAvailableException {\n    ResourceEncoder<X> resourceEncoder = resourceEncoderRegistry.get(resource.getResourceClass());\n    if (resourceEncoder != null) {\n      return resourceEncoder;\n    }\n    throw new NoResultEncoderAvailableException(resource.getResourceClass());\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  public <X> Encoder<X> getSourceEncoder(@NonNull X data) throws NoSourceEncoderAvailableException {\n    Encoder<X> encoder = encoderRegistry.getEncoder((Class<X>) data.getClass());\n    if (encoder != null) {\n      return encoder;\n    }\n    throw new NoSourceEncoderAvailableException(data.getClass());\n  }\n\n  @NonNull\n  public <X> DataRewinder<X> getRewinder(@NonNull X data) {\n    return dataRewinderRegistry.build(data);\n  }\n\n  @NonNull\n  public <Model> List<ModelLoader<Model, ?>> getModelLoaders(@NonNull Model model) {\n    return modelLoaderRegistry.getModelLoaders(model);\n  }\n\n  @NonNull\n  public List<ImageHeaderParser> getImageHeaderParsers() {\n    List<ImageHeaderParser> result = imageHeaderParserRegistry.getParsers();\n    if (result.isEmpty()) {\n      throw new NoImageHeaderParserException();\n    }\n    return result;\n  }\n\n  /**\n   * Thrown when no {@link com.bumptech.glide.load.model.ModelLoader} is registered for a given\n   * model class.\n   */\n  // Never serialized by Glide.\n  @SuppressWarnings(\"serial\")\n  public static class NoModelLoaderAvailableException extends MissingComponentException {\n\n    public NoModelLoaderAvailableException(@NonNull Object model) {\n      super(\"Failed to find any ModelLoaders registered for model class: \" + model.getClass());\n    }\n\n    public <M> NoModelLoaderAvailableException(\n        @NonNull M model, @NonNull List<ModelLoader<M, ?>> matchingButNotHandlingModelLoaders) {\n      super(\n          \"Found ModelLoaders for model class: \"\n              + matchingButNotHandlingModelLoaders\n              + \", but none that handle this specific model instance: \"\n              + model);\n    }\n\n    public NoModelLoaderAvailableException(\n        @NonNull Class<?> modelClass, @NonNull Class<?> dataClass) {\n      super(\"Failed to find any ModelLoaders for model: \" + modelClass + \" and data: \" + dataClass);\n    }\n  }\n\n  /** Thrown when no {@link ResourceEncoder} is registered for a given resource class. */\n  // Never serialized by Glide.\n  @SuppressWarnings(\"serial\")\n  public static class NoResultEncoderAvailableException extends MissingComponentException {\n    public NoResultEncoderAvailableException(@NonNull Class<?> resourceClass) {\n      super(\n          \"Failed to find result encoder for resource class: \"\n              + resourceClass\n              + \", you may need to consider registering a new Encoder for the requested type or\"\n              + \" DiskCacheStrategy.DATA/DiskCacheStrategy.NONE if caching your transformed\"\n              + \" resource is unnecessary.\");\n    }\n  }\n\n  /** Thrown when no {@link Encoder} is registered for a given data class. */\n  // Never serialized by Glide.\n  @SuppressWarnings(\"serial\")\n  public static class NoSourceEncoderAvailableException extends MissingComponentException {\n    public NoSourceEncoderAvailableException(@NonNull Class<?> dataClass) {\n      super(\"Failed to find source encoder for data class: \" + dataClass);\n    }\n  }\n\n  /** Thrown when some necessary component is missing for a load. */\n  // Never serialized by Glide.\n  @SuppressWarnings(\"serial\")\n  public static class MissingComponentException extends RuntimeException {\n    public MissingComponentException(@NonNull String message) {\n      super(message);\n    }\n  }\n\n  /** Thrown when no {@link ImageHeaderParser} is registered. */\n  // Never serialized by Glide.\n  @SuppressWarnings(\"serial\")\n  public static final class NoImageHeaderParserException extends MissingComponentException {\n    public NoImageHeaderParserException() {\n      super(\"Failed to find image header parser.\");\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/RegistryFactory.java",
    "content": "package com.bumptech.glide;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.ParcelFileDescriptor;\nimport androidx.annotation.Nullable;\nimport androidx.tracing.Trace;\nimport com.bumptech.glide.GlideBuilder.EnableImageDecoderForBitmaps;\nimport com.bumptech.glide.GlideBuilder.UseMediaStoreOpenFileApisIfPossible;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.data.InputStreamRewinder;\nimport com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.model.AssetUriLoader;\nimport com.bumptech.glide.load.model.ByteArrayLoader;\nimport com.bumptech.glide.load.model.ByteBufferEncoder;\nimport com.bumptech.glide.load.model.ByteBufferFileLoader;\nimport com.bumptech.glide.load.model.DataUrlLoader;\nimport com.bumptech.glide.load.model.DirectResourceLoader;\nimport com.bumptech.glide.load.model.FileLoader;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.MediaStoreFileLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.ResourceLoader;\nimport com.bumptech.glide.load.model.ResourceUriLoader;\nimport com.bumptech.glide.load.model.StreamEncoder;\nimport com.bumptech.glide.load.model.StringLoader;\nimport com.bumptech.glide.load.model.UnitModelLoader;\nimport com.bumptech.glide.load.model.UriLoader;\nimport com.bumptech.glide.load.model.UrlUriLoader;\nimport com.bumptech.glide.load.model.stream.HttpGlideUrlLoader;\nimport com.bumptech.glide.load.model.stream.MediaStoreImageThumbLoader;\nimport com.bumptech.glide.load.model.stream.MediaStoreVideoThumbLoader;\nimport com.bumptech.glide.load.model.stream.QMediaStoreUriLoader;\nimport com.bumptech.glide.load.model.stream.UrlLoader;\nimport com.bumptech.glide.load.resource.bitmap.BitmapDrawableDecoder;\nimport com.bumptech.glide.load.resource.bitmap.BitmapDrawableEncoder;\nimport com.bumptech.glide.load.resource.bitmap.BitmapEncoder;\nimport com.bumptech.glide.load.resource.bitmap.ByteBufferBitmapDecoder;\nimport com.bumptech.glide.load.resource.bitmap.ByteBufferBitmapImageDecoderResourceDecoder;\nimport com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;\nimport com.bumptech.glide.load.resource.bitmap.Downsampler;\nimport com.bumptech.glide.load.resource.bitmap.ExifInterfaceImageHeaderParser;\nimport com.bumptech.glide.load.resource.bitmap.InputStreamBitmapImageDecoderResourceDecoder;\nimport com.bumptech.glide.load.resource.bitmap.ParcelFileDescriptorBitmapDecoder;\nimport com.bumptech.glide.load.resource.bitmap.ResourceBitmapDecoder;\nimport com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder;\nimport com.bumptech.glide.load.resource.bitmap.UnitBitmapDecoder;\nimport com.bumptech.glide.load.resource.bitmap.VideoDecoder;\nimport com.bumptech.glide.load.resource.bytes.ByteBufferRewinder;\nimport com.bumptech.glide.load.resource.drawable.AnimatedImageDecoder;\nimport com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder;\nimport com.bumptech.glide.load.resource.drawable.UnitDrawableDecoder;\nimport com.bumptech.glide.load.resource.file.FileDecoder;\nimport com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.load.resource.gif.GifDrawableEncoder;\nimport com.bumptech.glide.load.resource.gif.GifFrameResourceDecoder;\nimport com.bumptech.glide.load.resource.gif.StreamGifDecoder;\nimport com.bumptech.glide.load.resource.transcode.BitmapBytesTranscoder;\nimport com.bumptech.glide.load.resource.transcode.BitmapDrawableTranscoder;\nimport com.bumptech.glide.load.resource.transcode.DrawableBytesTranscoder;\nimport com.bumptech.glide.load.resource.transcode.GifDrawableBytesTranscoder;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.module.GlideModule;\nimport com.bumptech.glide.util.GlideSuppliers.GlideSupplier;\nimport com.bumptech.glide.util.Synthetic;\nimport java.io.File;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.nio.ByteBuffer;\nimport java.util.List;\n\nfinal class RegistryFactory {\n\n  private RegistryFactory() {}\n\n  static GlideSupplier<Registry> lazilyCreateAndInitializeRegistry(\n      final Glide glide,\n      final List<GlideModule> manifestModules,\n      @Nullable final AppGlideModule annotationGeneratedModule) {\n    return new GlideSupplier<Registry>() {\n      // Rely on callers using memoization if they want to avoid duplicate work, but\n      // rely on ourselves to verify that no recursive initialization occurs.\n      private boolean isInitializing;\n\n      @Override\n      public Registry get() {\n        if (isInitializing) {\n          throw new IllegalStateException(\n              \"Recursive Registry initialization! In your\"\n                  + \" AppGlideModule and LibraryGlideModules, Make sure you're using the provided \"\n                  + \"Registry rather calling glide.getRegistry()!\");\n        }\n        Trace.beginSection(\"Glide registry\");\n        isInitializing = true;\n        try {\n          return createAndInitRegistry(glide, manifestModules, annotationGeneratedModule);\n        } finally {\n          isInitializing = false;\n          Trace.endSection();\n        }\n      }\n    };\n  }\n\n  @Synthetic\n  static Registry createAndInitRegistry(\n      Glide glide,\n      List<GlideModule> manifestModules,\n      @Nullable AppGlideModule annotationGeneratedModule) {\n\n    BitmapPool bitmapPool = glide.getBitmapPool();\n    ArrayPool arrayPool = glide.getArrayPool();\n    Context context = glide.getGlideContext().getApplicationContext();\n\n    GlideExperiments experiments = glide.getGlideContext().getExperiments();\n\n    Registry registry = new Registry();\n    initializeDefaults(context, registry, bitmapPool, arrayPool, experiments);\n    initializeModules(context, glide, registry, manifestModules, annotationGeneratedModule);\n    return registry;\n  }\n\n  private static void initializeDefaults(\n      Context context,\n      Registry registry,\n      BitmapPool bitmapPool,\n      ArrayPool arrayPool,\n      GlideExperiments experiments) {\n    registry.register(new DefaultImageHeaderParser());\n    // Right now we're only using this parser for HEIF images, which are only supported on OMR1+.\n    // If we need this for other file types, we should consider removing this restriction.\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {\n      registry.register(new ExifInterfaceImageHeaderParser());\n    }\n\n    final Resources resources = context.getResources();\n    List<ImageHeaderParser> imageHeaderParsers = registry.getImageHeaderParsers();\n\n    ByteBufferGifDecoder byteBufferGifDecoder =\n        new ByteBufferGifDecoder(context, imageHeaderParsers, bitmapPool, arrayPool);\n    ResourceDecoder<ParcelFileDescriptor, Bitmap> parcelFileDescriptorVideoDecoder =\n        VideoDecoder.parcel(bitmapPool);\n\n    // TODO(judds): Make ParcelFileDescriptorBitmapDecoder work with ImageDecoder.\n    Downsampler downsampler =\n        new Downsampler(\n            registry.getImageHeaderParsers(), resources.getDisplayMetrics(), bitmapPool, arrayPool);\n\n    ResourceDecoder<ByteBuffer, Bitmap> byteBufferBitmapDecoder;\n    ResourceDecoder<InputStream, Bitmap> streamBitmapDecoder;\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P\n        && experiments.isEnabled(EnableImageDecoderForBitmaps.class)) {\n      streamBitmapDecoder = new InputStreamBitmapImageDecoderResourceDecoder();\n      byteBufferBitmapDecoder = new ByteBufferBitmapImageDecoderResourceDecoder();\n    } else {\n      byteBufferBitmapDecoder = new ByteBufferBitmapDecoder(downsampler);\n      streamBitmapDecoder = new StreamBitmapDecoder(downsampler, arrayPool);\n    }\n\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n      registry.append(\n          Registry.BUCKET_ANIMATION,\n          InputStream.class,\n          Drawable.class,\n          AnimatedImageDecoder.streamDecoder(imageHeaderParsers, arrayPool));\n      registry.append(\n          Registry.BUCKET_ANIMATION,\n          ByteBuffer.class,\n          Drawable.class,\n          AnimatedImageDecoder.byteBufferDecoder(imageHeaderParsers, arrayPool));\n    }\n\n    ResourceDrawableDecoder resourceDrawableDecoder = new ResourceDrawableDecoder(context);\n\n    BitmapEncoder bitmapEncoder = new BitmapEncoder(arrayPool);\n\n    BitmapBytesTranscoder bitmapBytesTranscoder = new BitmapBytesTranscoder();\n    GifDrawableBytesTranscoder gifDrawableBytesTranscoder = new GifDrawableBytesTranscoder();\n\n    ContentResolver contentResolver = context.getContentResolver();\n\n    registry\n        .append(ByteBuffer.class, new ByteBufferEncoder())\n        .append(InputStream.class, new StreamEncoder(arrayPool))\n        /* Bitmaps */\n        .append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)\n        .append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder);\n\n    if (ParcelFileDescriptorRewinder.isSupported()) {\n      registry.append(\n          Registry.BUCKET_BITMAP,\n          ParcelFileDescriptor.class,\n          Bitmap.class,\n          new ParcelFileDescriptorBitmapDecoder(downsampler));\n    }\n\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n      registry.append(\n          Registry.BUCKET_BITMAP,\n          AssetFileDescriptor.class,\n          Bitmap.class,\n          VideoDecoder.asset(bitmapPool));\n    }\n\n    registry\n        .append(\n            Registry.BUCKET_BITMAP,\n            ParcelFileDescriptor.class,\n            Bitmap.class,\n            parcelFileDescriptorVideoDecoder)\n        .append(Bitmap.class, Bitmap.class, UnitModelLoader.Factory.<Bitmap>getInstance())\n        .append(Registry.BUCKET_BITMAP, Bitmap.class, Bitmap.class, new UnitBitmapDecoder())\n        .append(Bitmap.class, bitmapEncoder)\n        /* BitmapDrawables */\n        .append(\n            Registry.BUCKET_BITMAP_DRAWABLE,\n            ByteBuffer.class,\n            BitmapDrawable.class,\n            new BitmapDrawableDecoder<>(resources, byteBufferBitmapDecoder))\n        .append(\n            Registry.BUCKET_BITMAP_DRAWABLE,\n            InputStream.class,\n            BitmapDrawable.class,\n            new BitmapDrawableDecoder<>(resources, streamBitmapDecoder))\n        .append(\n            Registry.BUCKET_BITMAP_DRAWABLE,\n            ParcelFileDescriptor.class,\n            BitmapDrawable.class,\n            new BitmapDrawableDecoder<>(resources, parcelFileDescriptorVideoDecoder))\n        .append(BitmapDrawable.class, new BitmapDrawableEncoder(bitmapPool, bitmapEncoder))\n        /* GIFs */\n        .append(\n            Registry.BUCKET_ANIMATION,\n            InputStream.class,\n            GifDrawable.class,\n            new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool))\n        .append(\n            Registry.BUCKET_ANIMATION, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder)\n        .append(GifDrawable.class, new GifDrawableEncoder())\n        /* GIF Frames */\n        // Compilation with Gradle requires the type to be specified for UnitModelLoader here.\n        .append(\n            GifDecoder.class, GifDecoder.class, UnitModelLoader.Factory.<GifDecoder>getInstance())\n        .append(\n            Registry.BUCKET_BITMAP,\n            GifDecoder.class,\n            Bitmap.class,\n            new GifFrameResourceDecoder(bitmapPool))\n        /* Drawables */\n        .append(Uri.class, Drawable.class, resourceDrawableDecoder)\n        .append(\n            Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitmapPool))\n        /* Files */\n        .register(new ByteBufferRewinder.Factory())\n        .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())\n        .append(File.class, InputStream.class, new FileLoader.StreamFactory())\n        .append(File.class, File.class, new FileDecoder())\n        .append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())\n        // Compilation with Gradle requires the type to be specified for UnitModelLoader here.\n        .append(File.class, File.class, UnitModelLoader.Factory.<File>getInstance())\n        /* Models */\n        .register(new InputStreamRewinder.Factory(arrayPool));\n\n    if (ParcelFileDescriptorRewinder.isSupported()) {\n      registry.register(new ParcelFileDescriptorRewinder.Factory());\n    }\n\n    // DirectResourceLoader and ResourceUriLoader handle resource IDs and Uris owned by this\n    // package.\n    ModelLoaderFactory<Integer, InputStream> directResourceLoaderStreamFactory =\n        DirectResourceLoader.inputStreamFactory(context);\n    ModelLoaderFactory<Integer, AssetFileDescriptor>\n        directResourceLoaderAssetFileDescriptorFactory =\n            DirectResourceLoader.assetFileDescriptorFactory(context);\n    ModelLoaderFactory<Integer, Drawable> directResourceLaoderDrawableFactory =\n        DirectResourceLoader.drawableFactory(context);\n    registry\n        .append(int.class, InputStream.class, directResourceLoaderStreamFactory)\n        .append(Integer.class, InputStream.class, directResourceLoaderStreamFactory)\n        .append(\n            int.class, AssetFileDescriptor.class, directResourceLoaderAssetFileDescriptorFactory)\n        .append(\n            Integer.class,\n            AssetFileDescriptor.class,\n            directResourceLoaderAssetFileDescriptorFactory)\n        .append(int.class, Drawable.class, directResourceLaoderDrawableFactory)\n        .append(Integer.class, Drawable.class, directResourceLaoderDrawableFactory)\n        .append(Uri.class, InputStream.class, ResourceUriLoader.newStreamFactory(context))\n        .append(\n            Uri.class,\n            AssetFileDescriptor.class,\n            ResourceUriLoader.newAssetFileDescriptorFactory(context));\n\n    // ResourceLoader and UriLoader handle resource IDs and Uris owned by other packages.\n    ResourceLoader.UriFactory resourceLoaderUriFactory = new ResourceLoader.UriFactory(resources);\n    ResourceLoader.AssetFileDescriptorFactory resourceLoaderAssetFileDescriptorFactory =\n        new ResourceLoader.AssetFileDescriptorFactory(resources);\n    ResourceLoader.StreamFactory resourceLoaderStreamFactory =\n        new ResourceLoader.StreamFactory(resources);\n    registry\n        .append(Integer.class, Uri.class, resourceLoaderUriFactory)\n        .append(int.class, Uri.class, resourceLoaderUriFactory)\n        .append(Integer.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory)\n        .append(int.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory)\n        .append(Integer.class, InputStream.class, resourceLoaderStreamFactory)\n        .append(int.class, InputStream.class, resourceLoaderStreamFactory);\n\n    registry\n        .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>())\n        .append(Uri.class, InputStream.class, new DataUrlLoader.StreamFactory<Uri>())\n        .append(String.class, InputStream.class, new StringLoader.StreamFactory())\n        .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())\n        .append(\n            String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())\n        .append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets()))\n        .append(\n            Uri.class,\n            AssetFileDescriptor.class,\n            new AssetUriLoader.FileDescriptorFactory(context.getAssets()))\n        .append(Uri.class, InputStream.class, new MediaStoreImageThumbLoader.Factory(context))\n        .append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context));\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n      registry.append(\n          Uri.class, InputStream.class, new QMediaStoreUriLoader.InputStreamFactory(context));\n      registry.append(\n          Uri.class,\n          ParcelFileDescriptor.class,\n          new QMediaStoreUriLoader.FileDescriptorFactory(context));\n    }\n    boolean useMediaStoreOpenFileApisIfPossible =\n        experiments.isEnabled(UseMediaStoreOpenFileApisIfPossible.class);\n    registry\n        .append(\n            Uri.class,\n            InputStream.class,\n            new UriLoader.StreamFactory(contentResolver, useMediaStoreOpenFileApisIfPossible))\n        .append(\n            Uri.class,\n            ParcelFileDescriptor.class,\n            new UriLoader.FileDescriptorFactory(\n                contentResolver, useMediaStoreOpenFileApisIfPossible))\n        .append(\n            Uri.class,\n            AssetFileDescriptor.class,\n            new UriLoader.AssetFileDescriptorFactory(\n                contentResolver, useMediaStoreOpenFileApisIfPossible))\n        .append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())\n        .append(URL.class, InputStream.class, new UrlLoader.StreamFactory())\n        .append(Uri.class, File.class, new MediaStoreFileLoader.Factory(context))\n        .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())\n        .append(byte[].class, ByteBuffer.class, new ByteArrayLoader.ByteBufferFactory())\n        .append(byte[].class, InputStream.class, new ByteArrayLoader.StreamFactory())\n        .append(Uri.class, Uri.class, UnitModelLoader.Factory.<Uri>getInstance())\n        .append(Drawable.class, Drawable.class, UnitModelLoader.Factory.<Drawable>getInstance())\n        .append(Drawable.class, Drawable.class, new UnitDrawableDecoder())\n        /* Transcoders */\n        .register(Bitmap.class, BitmapDrawable.class, new BitmapDrawableTranscoder(resources))\n        .register(Bitmap.class, byte[].class, bitmapBytesTranscoder)\n        .register(\n            Drawable.class,\n            byte[].class,\n            new DrawableBytesTranscoder(\n                bitmapPool, bitmapBytesTranscoder, gifDrawableBytesTranscoder))\n        .register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder);\n\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n      ResourceDecoder<ByteBuffer, Bitmap> byteBufferVideoDecoder =\n          VideoDecoder.byteBuffer(bitmapPool);\n      registry.append(ByteBuffer.class, Bitmap.class, byteBufferVideoDecoder);\n      registry.append(\n          ByteBuffer.class,\n          BitmapDrawable.class,\n          new BitmapDrawableDecoder<>(resources, byteBufferVideoDecoder));\n    }\n  }\n\n  private static void initializeModules(\n      Context context,\n      Glide glide,\n      Registry registry,\n      List<GlideModule> manifestModules,\n      @Nullable AppGlideModule annotationGeneratedModule) {\n    for (GlideModule module : manifestModules) {\n      try {\n        module.registerComponents(context, glide, registry);\n      } catch (AbstractMethodError e) {\n        throw new IllegalStateException(\n            \"Attempting to register a Glide v3 module. If you see this, you or one of your\"\n                + \" dependencies may be including Glide v3 even though you're using Glide v4.\"\n                + \" You'll need to find and remove (or update) the offending dependency.\"\n                + \" The v3 module name is: \"\n                + module.getClass().getName(),\n            e);\n      }\n    }\n    if (annotationGeneratedModule != null) {\n      annotationGeneratedModule.registerComponents(context, glide, registry);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/RequestBuilder.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.request.RequestOptions.diskCacheStrategyOf;\nimport static com.bumptech.glide.request.RequestOptions.skipMemoryCacheOf;\n\nimport android.annotation.SuppressLint;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.Resources.Theme;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.widget.ImageView;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.ErrorRequestCoordinator;\nimport com.bumptech.glide.request.FutureTarget;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.RequestCoordinator;\nimport com.bumptech.glide.request.RequestFutureTarget;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.SingleRequest;\nimport com.bumptech.glide.request.ThumbnailRequestCoordinator;\nimport com.bumptech.glide.request.target.PreloadTarget;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.target.ViewTarget;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.signature.AndroidResourceSignature;\nimport com.bumptech.glide.util.Executors;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Util;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.Executor;\n\n/**\n * A generic class that can handle setting options and staring loads for generic resource types.\n *\n * @param <TranscodeType> The type of resource that will be delivered to the {@link\n *     com.bumptech.glide.request.target.Target}.\n */\n// Public API.\n@SuppressWarnings({\"unused\", \"WeakerAccess\"})\npublic class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>\n    implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> {\n  // Used in generated subclasses\n  protected static final RequestOptions DOWNLOAD_ONLY_OPTIONS =\n      new RequestOptions()\n          .diskCacheStrategy(DiskCacheStrategy.DATA)\n          .priority(Priority.LOW)\n          .skipMemoryCache(true);\n\n  private final Context context;\n  private final RequestManager requestManager;\n  private final Class<TranscodeType> transcodeClass;\n  private final Glide glide;\n  private final GlideContext glideContext;\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  private TransitionOptions<?, ? super TranscodeType> transitionOptions;\n\n  @Nullable private Object model;\n  // model may occasionally be null, so to enforce that load() was called, put a boolean rather\n  // than relying on model not to be null.\n  @Nullable private List<RequestListener<TranscodeType>> requestListeners;\n  @Nullable private RequestBuilder<TranscodeType> thumbnailBuilder;\n  @Nullable private RequestBuilder<TranscodeType> errorBuilder;\n  @Nullable private Float thumbSizeMultiplier;\n  private boolean isDefaultTransitionOptionsSet = true;\n  private boolean isModelSet;\n\n  private boolean isThumbnailBuilt;\n\n  // We only override the method to change the return type, not the functionality.\n  @SuppressLint(\"CheckResult\")\n  @SuppressWarnings(\"PMD.ConstructorCallsOverridableMethod\")\n  protected RequestBuilder(\n      @NonNull Glide glide,\n      RequestManager requestManager,\n      Class<TranscodeType> transcodeClass,\n      Context context) {\n    this.glide = glide;\n    this.requestManager = requestManager;\n    this.transcodeClass = transcodeClass;\n    this.context = context;\n    this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass);\n    this.glideContext = glide.getGlideContext();\n\n    initRequestListeners(requestManager.getDefaultRequestListeners());\n    apply(requestManager.getDefaultRequestOptions());\n  }\n\n  RequestManager getRequestManager() {\n    return requestManager;\n  }\n\n  @SuppressLint(\"CheckResult\")\n  @SuppressWarnings(\"PMD.ConstructorCallsOverridableMethod\")\n  protected RequestBuilder(Class<TranscodeType> transcodeClass, RequestBuilder<?> other) {\n    this(other.glide, other.requestManager, transcodeClass, other.context);\n    model = other.model;\n    isModelSet = other.isModelSet;\n\n    // This is safe because it will always mutate, no one else has access to the object.\n    apply(other);\n  }\n\n  // Casting from Object to a specific type is always safe.\n  @SuppressWarnings(\"unchecked\")\n  // addListener always returns the same instance.\n  @SuppressLint(\"CheckResult\")\n  private void initRequestListeners(List<RequestListener<Object>> requestListeners) {\n    for (RequestListener<Object> listener : requestListeners) {\n      addListener((RequestListener<TranscodeType>) listener);\n    }\n  }\n\n  /**\n   * Applies the given options to the request.\n   *\n   * <p>As with {@link RequestOptions#apply(BaseRequestOptions)}, {@code #apply} only replaces those\n   * values that are explicitly set in the given {@link RequestOptions} object. If you need to\n   * completely reset all previously set options, create a new {@code RequestBuilder} instead of\n   * using this method.\n   *\n   * @see RequestOptions#apply(BaseRequestOptions)\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> apply(@NonNull BaseRequestOptions<?> requestOptions) {\n    Preconditions.checkNotNull(requestOptions);\n    return super.apply(requestOptions);\n  }\n\n  /**\n   * Sets the {@link TransitionOptions} to use to transition from the placeholder or thumbnail when\n   * this load completes.\n   *\n   * <p>The given {@link TransitionOptions} will replace any {@link TransitionOptions} set\n   * previously.\n   *\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<TranscodeType> transition(\n      @NonNull TransitionOptions<?, ? super TranscodeType> transitionOptions) {\n    if (isAutoCloneEnabled()) {\n      return clone().transition(transitionOptions);\n    }\n    this.transitionOptions = Preconditions.checkNotNull(transitionOptions);\n    isDefaultTransitionOptionsSet = false;\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets a {@link RequestListener} to monitor the resource load and removes all previously set\n   * listeners (either via this method or from {@link #addListener(RequestListener)} .\n   *\n   * <p>Calls to this method will replace previously set listeners. To set multiple listeners, use\n   * {@link #addListener} instead.\n   *\n   * @param requestListener The request listener to use.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings(\"unchecked\")\n  public RequestBuilder<TranscodeType> listener(\n      @Nullable RequestListener<TranscodeType> requestListener) {\n    if (isAutoCloneEnabled()) {\n      return clone().listener(requestListener);\n    }\n    this.requestListeners = null;\n    return addListener(requestListener);\n  }\n\n  /**\n   * Adds a {@link RequestListener} to the list that will be called in the order they were added\n   * when the request ends.\n   *\n   * <p>Multiple calls to this method append additional listeners. Previous listeners are not\n   * removed. If you want to replace any previously added listeners, use {@link\n   * #listener(RequestListener)}.\n   *\n   * <p>Listeners track the state of the request started by this particular {@code builder}. When\n   * used with the thumbnail APIs ({@link #thumbnail(RequestBuilder)}) this can start to seem\n   * confusing because multiple requests are running and each may succeed or fail, independent of\n   * each other. As a rule, Glide does not add {@link RequestListener}s to thumbnail requests\n   * automatically. That means that {@link RequestListener}s track the state of exactly one request\n   * in the chain. For example, if you start a primary request with a single nested thumbnail and\n   * you add a {@link RequestListener} only to the primary request, then the {@link RequestListener}\n   * will only be notified when the primary request succeeds or fails. If the thumbnail succeeds,\n   * but the primary request fails, the {@link RequestListener} added to the primary request will\n   * still be called with {@link RequestListener#onLoadFailed(GlideException, Object, Target,\n   * boolean)}. In the same scenario, the {@link RequestListener} added only to the primary request\n   * will not have {@link RequestListener#onResourceReady(Object, Object, Target, DataSource,\n   * boolean)} called when the thumbnail request finishes successfully. Similarly, if you add a\n   * {@link RequestListener} only to a thumbnail request, but not the primary request, that {@code\n   * listener} will only be called for changes related to the thumbnail request. If the thumbnail\n   * request fails, the {@code listener} added to the thumbnail request will be immediately called\n   * via {@link RequestListener#onLoadFailed(GlideException, Object, Target, boolean)}, even though\n   * the primary request may eventually succeed. It is perfectly possible to add a {@link\n   * RequestListener} to both the primary and a thumbnail request. If you do so, the {@link\n   * RequestListener} will be called independently for each request when it finishes. Keep in mind\n   * that if any parent request finishes before its thumbnail request(s), it will attempt to cancel\n   * those requests. As a result there's no guarantee that a {@link RequestListener} added to a\n   * thumbnail request will actually be called with either success or failure. These same patterns\n   * hold for arbitrarily nested thumbnails. The {@code listener} is only called for the requests it\n   * is added to and may not be called for every thumbnail request if those requests are cancelled\n   * due to the completion of a parent request.\n   *\n   * <p>The one exception to the rules about thumbnails is {@link #thumbnail(float)}. In this case\n   * we appear to be passing {@link RequestListener}s added to the parent request to the generated\n   * thumbnail requests. To try to reduce confusion, the {@link #thumbnail(float)} method has been\n   * deprecated. It can be easily replicated using {@link #thumbnail(RequestBuilder)} and {@link\n   * BaseRequestOptions#sizeMultiplier(float)}.\n   *\n   * <p>Often in UIs it's desirable to try to track the overall status of a request, including the\n   * thumbnails. For example, you might want to load an image, start an animation if the\n   * asynchronous image load succeeds and perform some fallback action if it fails. If you're using\n   * a single primary request, {@link RequestListener} will work for this. However, if you then\n   * decide to try to make things more performant by adding a thumbnail (or multiple thumbnails),\n   * {@link RequestListener} is awkward because either you only add it to the main request and it's\n   * not called when the thumbnails complete (which defeats the purpose) or it's called for every\n   * request and it's hard to keep track of when the overall request has failed. A better option\n   * than using {@link RequestListener} to track the state of the UI then is to use {@link Target}\n   * instead. {@link Target#onResourceReady(Object, Transition)} will be called when any thumbnail\n   * finishes, which you can use to trigger your animation starting. {@link\n   * Target#onLoadFailed(Drawable)} will only be called if every request in the chain, including the\n   * primary request, fails, which you can use to trigger your fallback behavior. Be sure to pick an\n   * appropriate {@link Target} subclass when possible, like {@link\n   * com.bumptech.glide.request.target.BitmapImageViewTarget} or {@link\n   * com.bumptech.glide.request.target.DrawableImageViewTarget} when loading into {@link ImageView}\n   * or {@link com.bumptech.glide.request.target.CustomTarget} when using custom rendering. Don't\n   * forget to call {@code super()} in the {@code ImageViewTarget}s.\n   *\n   * <p>It's best to create a single instance of an exception handler per type of request (usually\n   * activity/fragment) rather than pass one in per request to avoid some redundant object\n   * allocation.\n   *\n   * @param requestListener The request listener to use. If {@code null}, this method is a noop.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<TranscodeType> addListener(\n      @Nullable RequestListener<TranscodeType> requestListener) {\n    if (isAutoCloneEnabled()) {\n      return clone().addListener(requestListener);\n    }\n    if (requestListener != null) {\n      if (this.requestListeners == null) {\n        this.requestListeners = new ArrayList<>();\n      }\n      this.requestListeners.add(requestListener);\n    }\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets a {@link RequestBuilder} that is built and run if the load started by this {@link\n   * RequestBuilder} fails.\n   *\n   * <p>If this {@link RequestBuilder} uses a thumbnail that succeeds the given error {@link\n   * RequestBuilder} will be started anyway if the non-thumbnail request fails.\n   *\n   * <p>Recursive calls to this method as well as calls to {@link #thumbnail(float)} and {@link\n   * #thumbnail(RequestBuilder)} are supported for the given error {@link RequestBuilder}.\n   *\n   * <p>Unlike {@link #thumbnail(RequestBuilder)} and {@link #thumbnail(float)}, no options from\n   * this primary {@link RequestBuilder} are propagated to the given error {@link RequestBuilder}.\n   * Options like priority, override widths and heights and transitions must be applied\n   * independently to the error builder.\n   *\n   * <p>The given {@link RequestBuilder} will start and potentially override a fallback drawable if\n   * it's set on this {@link RequestBuilder} via {@link\n   * RequestOptions#fallback(android.graphics.drawable.Drawable)} or {@link\n   * RequestOptions#fallback(int)}.\n   *\n   * @return This {@link RequestBuilder}.\n   */\n  @NonNull\n  public RequestBuilder<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> errorBuilder) {\n    if (isAutoCloneEnabled()) {\n      return clone().error(errorBuilder);\n    }\n    this.errorBuilder = errorBuilder;\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Identical to calling {@link #error(RequestBuilder)} where the {@code RequestBuilder} is the\n   * result of calling {@link #clone()} and removing any existing thumbnail and error {@code\n   * RequestBuilders}.\n   *\n   * <p>Other than thumbnail and error {@code RequestBuilder}s, which are removed, all other options\n   * are retained from the primary request. However, <b>order matters!</b> Any options applied after\n   * this method is called will not be applied to the error {@code RequestBuilder}.\n   *\n   * <p>WARNING: Calling this method with a {@code model} whose type does not match the type of the\n   * model passed to {@code load()} may be dangerous! Any options that were applied by the various\n   * type specific {@code load()} methods, like {@link #load(byte[])} will be copied to the error\n   * request here even if the {@code model} you pass to this method doesn't match. Similary, options\n   * that would be normally applied by type specific {@code load()} methods will <em>not</em> be\n   * applied to this request. If this behavior is confusing or unexpected, use {@link\n   * #error(RequestBuilder)} instead.\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<TranscodeType> error(Object model) {\n    if (model == null) {\n      return error((RequestBuilder<TranscodeType>) null);\n    }\n    return error(cloneWithNullErrorAndThumbnail().load(model));\n  }\n\n  private RequestBuilder<TranscodeType> cloneWithNullErrorAndThumbnail() {\n    return clone()\n        .error((RequestBuilder<TranscodeType>) null)\n        .thumbnail((RequestBuilder<TranscodeType>) null);\n  }\n\n  /**\n   * Loads and displays the resource retrieved by the given thumbnail request if it finishes before\n   * this request. Best used for loading thumbnail resources that are smaller and will be loaded\n   * more quickly than the full size resource. There are no guarantees about the order in which the\n   * requests will actually finish. However, if the thumb request completes after the full request,\n   * the thumb resource will never replace the full resource.\n   *\n   * <p>Recursive calls to thumbnail are supported.\n   *\n   * <p>Overrides any previous calls to this method, {@link #thumbnail(float)} and {@link\n   * #thumbnail(RequestBuilder[])}.\n   *\n   * @see #thumbnail(float)\n   * @see #thumbnail(RequestBuilder[])\n   * @param thumbnailRequest The request to use to load the thumbnail.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings(\"unchecked\")\n  public RequestBuilder<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType> thumbnailRequest) {\n    if (isAutoCloneEnabled()) {\n      return clone().thumbnail(thumbnailRequest);\n    }\n    this.thumbnailBuilder = thumbnailRequest;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Recursively applies {@link #thumbnail(RequestBuilder)} so that the {@link RequestBuilder}s are\n   * loaded as thumbnails in the given priority order.\n   *\n   * <p>{@link #thumbnail(RequestBuilder)} is applied in the order given so that the {@link\n   * RequestBuilder} at position 0 has the {@link RequestBuilder} at position 1 applied as using its\n   * thumbnail method, the {@link RequestBuilder} at position 1 has the {@link RequestBuilder} at\n   * position 2 applied using its thumbnail method and so on.\n   *\n   * <p>Calling this method with an {@code null} array of {@link RequestBuilder} thumbnails or an\n   * empty array of {@link RequestBuilder} thumbnails is equivalent to calling {@link\n   * #thumbnail(RequestBuilder)} with {@code null}.\n   *\n   * <p>Any individual {@link RequestBuilder} in the array of thumbnails provided here may be {@code\n   * null}. {@code null} {@link RequestBuilder}s are ignored and excluded from the recursive chain.\n   *\n   * <p>The {@link RequestBuilder} objects provided here may be mutated and have any previous calls\n   * to this method or {@link #thumbnail(RequestBuilder)} methods overridden.\n   *\n   * <p>Overrides any previous calls to {@link #thumbnail(RequestBuilder)}, {@link\n   * #thumbnail(float)} and this method.\n   *\n   * @see #thumbnail(float)\n   * @see #thumbnail(RequestBuilder)\n   * @return This request builder.\n   */\n  @SuppressWarnings({\"CheckResult\", \"unchecked\"})\n  @NonNull\n  @CheckResult\n  public RequestBuilder<TranscodeType> thumbnail(\n      @Nullable RequestBuilder<TranscodeType>... thumbnails) {\n    if (thumbnails == null || thumbnails.length == 0) {\n      return thumbnail((RequestBuilder<TranscodeType>) null);\n    }\n\n    return thumbnail(Arrays.asList(thumbnails));\n  }\n\n  /**\n   * Recursively applies {@link #thumbnail(RequestBuilder)} so that the {@link RequestBuilder}s are\n   * loaded as thumbnails in the given priority order.\n   *\n   * <p>{@link #thumbnail(RequestBuilder)} is applied in the order given so that the {@link\n   * RequestBuilder} at position 0 has the {@link RequestBuilder} at position 1 applied as using its\n   * thumbnail method, the {@link RequestBuilder} at position 1 has the {@link RequestBuilder} at\n   * position 2 applied using its thumbnail method and so on.\n   *\n   * <p>Calling this method with a {@code null} list of {@link RequestBuilder} thumbnails or an\n   * empty list of {@link RequestBuilder} thumbnails is equivalent to calling {@link\n   * #thumbnail(RequestBuilder)} with {@code null}.\n   *\n   * <p>Any individual {@link RequestBuilder} in the list of thumbnails provided here may be {@code\n   * null}. {@code null} {@link RequestBuilder}s are ignored and excluded from the recursive chain.\n   *\n   * <p>The {@link RequestBuilder} objects provided here may be mutated and have any previous calls\n   * to this method or {@link #thumbnail(RequestBuilder)} methods overridden.\n   *\n   * <p>Overrides any previous calls to {@link #thumbnail(RequestBuilder)}, {@link\n   * #thumbnail(float)} and this method.\n   *\n   * @see #thumbnail(float)\n   * @see #thumbnail(RequestBuilder)\n   * @return This request builder.\n   */\n  @SuppressWarnings({\"CheckResult\", \"unchecked\"})\n  @NonNull\n  @CheckResult\n  public RequestBuilder<TranscodeType> thumbnail(\n      @Nullable List<RequestBuilder<TranscodeType>> thumbnails) {\n    if (thumbnails == null || thumbnails.isEmpty()) {\n      return thumbnail((RequestBuilder<TranscodeType>) null);\n    }\n\n    RequestBuilder<TranscodeType> previous = null;\n\n    // Start with the lowest priority thumbnail so that we can safely handle mutations if\n    // autoClone() is enabled by assigning the result of calling thumbnail() during the iteration.\n    // Starting with the highest priority thumbnail would prevent us from assigning the result of\n    // thumbnail because the mutated request wouldn't be used in the next iteration.\n    for (int i = thumbnails.size() - 1; i >= 0; i--) {\n      RequestBuilder<TranscodeType> current = thumbnails.get(i);\n      // Ignore null thumbnails.\n      if (current == null) {\n        continue;\n      }\n\n      if (previous == null) {\n        // If we don't yet have our first non-null request, set it and continue.\n        previous = current;\n      } else {\n        // Otherwise make our next lowest priority request the thumbnail of our current request.\n        previous = current.thumbnail(previous);\n      }\n    }\n    return thumbnail(previous);\n  }\n\n  /**\n   * Loads a resource in an identical manner to this request except with the dimensions of the\n   * target multiplied by the given size multiplier. If the thumbnail load completes before the full\n   * size load, the thumbnail will be shown. If the thumbnail load completes after the full size\n   * load, the thumbnail will not be shown.\n   *\n   * <p>Note - The thumbnail resource will be smaller than the size requested so the target (or\n   * {@link ImageView}) must be able to scale the thumbnail appropriately. See {@link\n   * android.widget.ImageView.ScaleType}.\n   *\n   * <p>Almost all options will be copied from the original load, including the {@link\n   * com.bumptech.glide.load.model.ModelLoader}, {@link com.bumptech.glide.load.ResourceDecoder},\n   * and {@link com.bumptech.glide.load.Transformation}s. However, {@link\n   * com.bumptech.glide.request.RequestOptions#placeholder(int)} and {@link\n   * com.bumptech.glide.request.RequestOptions#error(int)}, and {@link #listener(RequestListener)}\n   * will only be used on the full size load and will not be copied for the thumbnail load.\n   *\n   * <p>Recursive calls to thumbnail are supported.\n   *\n   * <p>Overrides any previous calls to this method, {@link #thumbnail(RequestBuilder[])}, and\n   * {@link #thumbnail(RequestBuilder)}.\n   *\n   * @see #thumbnail(RequestBuilder)\n   * @see #thumbnail(RequestBuilder[])\n   * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading\n   *     the thumbnail.\n   * @return This request builder.\n   * @deprecated The behavior differences between this method and {@link #thumbnail(RequestBuilder)}\n   *     are subtle, hard to understand for users and hard to maintain for developers. See the\n   *     javadoc on {@link #listener(RequestListener)} for one concrete example of the behavior\n   *     differences and complexity introduced by this method. Better consistency and readability\n   *     can be obtained by calling {@link #thumbnail(RequestBuilder)} with a duplicate {@code\n   *     RequestBuilder} on which you have called {@link BaseRequestOptions#sizeMultiplier(float)}.\n   *     In practice this method also isn't especially useful. It's much more common to want to\n   *     specify a number of different attributes for thumbnails than just a simple percentage\n   *     modifier on the target size, so there's little justification for keeping this method. This\n   *     method will be removed in a future version of Glide.\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings(\"unchecked\")\n  @Deprecated\n  public RequestBuilder<TranscodeType> thumbnail(float sizeMultiplier) {\n    if (isAutoCloneEnabled()) {\n      return clone().thumbnail(sizeMultiplier);\n    }\n    if (sizeMultiplier < 0f || sizeMultiplier > 1f) {\n      throw new IllegalArgumentException(\"sizeMultiplier must be between 0 and 1\");\n    }\n    this.thumbSizeMultiplier = sizeMultiplier;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets the specific model to load data for.\n   *\n   * @param model The model to load data for, or null.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public RequestBuilder<TranscodeType> load(@Nullable Object model) {\n    return loadGeneric(model);\n  }\n\n  @NonNull\n  private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {\n    if (isAutoCloneEnabled()) {\n      return clone().loadGeneric(model);\n    }\n    this.model = model;\n    isModelSet = true;\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Returns an object to load the given {@link Bitmap}.\n   *\n   * <p>It's almost always better to allow Glide to load {@link Bitmap}s than pass {@link Bitmap}s\n   * into Glide. If you have a custom way to obtain {@link Bitmap}s that is not supported by Glide\n   * by default, consider registering a custom {@link com.bumptech.glide.load.model.ModelLoader} or\n   * {@link com.bumptech.glide.load.ResourceDecoder} instead of using this method.\n   *\n   * <p>The {@link DiskCacheStrategy} is set to {@link DiskCacheStrategy#NONE}. Previous calls to\n   * {@link #apply(BaseRequestOptions)} or previously applied {@link DiskCacheStrategy}s will be\n   * overridden by this method. Applying an {@link DiskCacheStrategy} other than {@link\n   * DiskCacheStrategy#NONE} after calling this method may result in undefined behavior.\n   *\n   * <p>In memory caching relies on Object equality. The contents of the {@link Bitmap}s are not\n   * compared.\n   *\n   * @see #load(Object)\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> load(@Nullable Bitmap bitmap) {\n    return loadGeneric(bitmap).apply(diskCacheStrategyOf(DiskCacheStrategy.NONE));\n  }\n\n  /**\n   * Returns a request builder to load the given {@link Drawable}.\n   *\n   * <p>It's almost always better to allow Glide to load {@link Bitmap}s than to pass {@link\n   * Bitmap}s into Glide using this method . If you have a custom way to obtain {@link Bitmap}s that\n   * is not supported by Glide by default, consider registering a custom {@link\n   * com.bumptech.glide.load.model.ModelLoader} or {@link com.bumptech.glide.load.ResourceDecoder}\n   * instead of using this method.\n   *\n   * <p>The {@link DiskCacheStrategy} is set to {@link DiskCacheStrategy#NONE}. Previous calls to\n   * {@link #apply(BaseRequestOptions)} or previously applied {@link DiskCacheStrategy}s will be\n   * overridden by this method. Applying an {@link DiskCacheStrategy} other than {@link\n   * DiskCacheStrategy#NONE} after calling this method may result in undefined behavior.\n   *\n   * <p>In memory caching relies on Object equality. The contents of the {@link Drawable}s are not\n   * compared.\n   *\n   * @see #load(Object)\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> load(@Nullable Drawable drawable) {\n    return loadGeneric(drawable).apply(diskCacheStrategyOf(DiskCacheStrategy.NONE));\n  }\n\n  /**\n   * Returns a request builder to load the given {@link java.lang.String}.\n   *\n   * <p>Note - this method caches data using only the given String as the cache key. If the data is\n   * a Uri outside of your control, or you otherwise expect the data represented by the given String\n   * to change without the String identifier changing, Consider using {@link\n   * com.bumptech.glide.request.RequestOptions#signature(com.bumptech.glide.load.Key)} to mixin a\n   * signature you create that identifies the data currently at the given String that will\n   * invalidate the cache if that data changes. Alternatively, using {@link\n   * com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or {@link\n   * com.bumptech.glide.request.RequestOptions#skipMemoryCache(boolean)} may be appropriate.\n   *\n   * <p>If {@code string} is in fact a resource {@link Uri}, you should first parse it to a Uri\n   * using {@link Uri#parse(String)} and then pass the {@code Uri} to {@link #load(Uri)}. Doing so\n   * will ensure that we respect the appropriate theme / dark / light mode. As an alternative, you\n   * can also manually apply the current {@link Theme} using {@link #theme(Theme)}.\n   *\n   * @see #load(Object)\n   * @param string A file path, or a uri or url handled by {@link\n   *     com.bumptech.glide.load.model.UriLoader}.\n   */\n  @NonNull\n  @Override\n  @CheckResult\n  public RequestBuilder<TranscodeType> load(@Nullable String string) {\n    return loadGeneric(string);\n  }\n\n  /**\n   * Returns a request builder to load the given {@link Uri}.\n   *\n   * <p>Note - this method caches data at Uris using only the Uri itself as the cache key. The data\n   * represented by Uris from some content providers may change without the Uri changing, which\n   * means using this method can lead to displaying stale data. Consider using {@link\n   * com.bumptech.glide.request.RequestOptions#signature(com.bumptech.glide.load.Key)} to mixin a\n   * signature you create based on the data at the given Uri that will invalidate the cache if that\n   * data changes. Alternatively, using {@link\n   * com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or {@link\n   * com.bumptech.glide.request.RequestOptions#skipMemoryCache(boolean)} may be appropriate. The\n   * only exception to this is that if we recognize the given {@code uri} as having {@link\n   * ContentResolver#SCHEME_ANDROID_RESOURCE}, then we'll apply {@link AndroidResourceSignature}\n   * automatically. If we do so, calls to other {@code load()} methods will <em>not</em> override\n   * the automatically applied signature.\n   *\n   * <p>If {@code uri} has a {@link Uri#getScheme()} of {@link\n   * android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}, then this method will add the {@link\n   * android.content.res.Resources.Theme} of the {@link Context} associated with this {@code\n   * requestBuilder} so that we can respect themeable attributes and/or light / dark mode. Any call\n   * to {@link #theme(Theme)} prior to this method call will be overridden. To avoid this, call\n   * {@link #theme(Theme)} after calling this method with either {@code null} or the {@code Theme}\n   * you'd prefer to use instead. Note that even if you change the theme, the {@link\n   * AndroidResourceSignature} will still be based on the {@link Context} theme.\n   *\n   * @see #load(Object)\n   * @param uri The Uri representing the image. Must be of a type handled by {@link\n   *     com.bumptech.glide.load.model.UriLoader}.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> load(@Nullable Uri uri) {\n    return maybeApplyOptionsResourceUri(uri, loadGeneric(uri));\n  }\n\n  private RequestBuilder<TranscodeType> maybeApplyOptionsResourceUri(\n      @Nullable Uri uri, RequestBuilder<TranscodeType> requestBuilder) {\n    if (uri == null || !ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {\n      return requestBuilder;\n    }\n    return applyResourceThemeAndSignature(requestBuilder);\n  }\n\n  private RequestBuilder<TranscodeType> applyResourceThemeAndSignature(\n      RequestBuilder<TranscodeType> requestBuilder) {\n    return requestBuilder\n        .theme(context.getTheme())\n        .signature(AndroidResourceSignature.obtain(context));\n  }\n\n  /**\n   * Returns a request builder to load the given {@link File}.\n   *\n   * <p>Note - this method caches data for Files using only the file path itself as the cache key.\n   * The data in the File can change so using this method can lead to displaying stale data. If you\n   * expect the data in the File to change, Consider using {@link\n   * com.bumptech.glide.request.RequestOptions#signature(com.bumptech.glide.load.Key)} to mixin a\n   * signature you create that identifies the data currently in the File that will invalidate the\n   * cache if that data changes. Alternatively, using {@link\n   * com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or {@link\n   * com.bumptech.glide.request.RequestOptions#skipMemoryCache(boolean)} may be appropriate.\n   *\n   * @see #load(Object)\n   * @param file The File containing the image\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> load(@Nullable File file) {\n    return loadGeneric(file);\n  }\n\n  /**\n   * Returns a request builder that uses the {@link\n   * com.bumptech.glide.load.model.ModelLoaderFactory} currently registered or {@link Integer} to\n   * load the image represented by the given {@link Integer} resource id. Defaults to {@link\n   * com.bumptech.glide.load.model.ResourceLoader} to load resource id models.\n   *\n   * <p>By default this method adds a version code and night mode based signature to the cache key\n   * used to cache this resource in Glide. This signature is sufficient to guarantee that end users\n   * will see the most up to date versions of your Drawables, but during development if you do not\n   * increment your version code before each install and you replace a Drawable with different data\n   * without changing the Drawable name, you may see inconsistent cached data. To get around this,\n   * consider using {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} via {@link\n   * RequestOptions#diskCacheStrategy(com.bumptech.glide.load.engine.DiskCacheStrategy)} during\n   * development, and re-enabling the default {@link\n   * com.bumptech.glide.load.engine.DiskCacheStrategy#RESOURCE} for release builds.\n   *\n   * <p>This method will load non-{@link android.graphics.Bitmap} resources like {@link\n   * android.graphics.drawable.VectorDrawable}s. Although Glide makes a best effort to apply {@link\n   * com.bumptech.glide.load.Transformation}s to these {@link Drawable}s by either extracting the\n   * underlying {@link Bitmap} or by converting the {@link Drawable} to a {@link Bitmap}, Glide is\n   * still not able to transform all types of resources. Animated {@link Drawable}s cannot be\n   * transformed (other than {@link com.bumptech.glide.load.resource.gif.GifDrawable}). To avoid\n   * load failures if a {@link Drawable} can't be transformed, use the optional transformation\n   * methods like {@link RequestOptions#optionalTransform(Class, Transformation)}.\n   *\n   * <p>In some cases converting {@link Drawable}s to {@link Bitmap}s may be inefficient. Use this\n   * method, especially in conjunction with {@link com.bumptech.glide.load.Transformation}s with\n   * caution for non-{@link Bitmap} {@link Drawable}s.\n   *\n   * <p>This method will add the {@link android.content.res.Resources.Theme} of the {@link Context}\n   * associated with this {@code requestBuilder} so that we can respect themeable attributes and/or\n   * light / dark mode. Any call to {@link #theme(Theme)} prior to this method call will be\n   * overridden. To avoid this, call {@link #theme(Theme)} after calling this method with either\n   * {@code null} or the {@code Theme} you'd prefer to use instead. Note that even if you change the\n   * theme, the {@link AndroidResourceSignature} will still be based on the {@link Context} theme.\n   *\n   * @see #load(Integer)\n   * @see com.bumptech.glide.signature.AndroidResourceSignature\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer resourceId) {\n    return applyResourceThemeAndSignature(loadGeneric(resourceId));\n  }\n\n  /**\n   * Returns a request builder to load the given {@link URL}.\n   *\n   * @param url The URL representing the image.\n   * @see #load(Object)\n   * @deprecated The {@link java.net.URL} class has <a href=\"http://goo.gl/c4hHNu\">a number of\n   *     performance problems</a> and should generally be avoided when possible. Prefer {@link\n   *     #load(android.net.Uri)} or {@link #load(String)}.\n   */\n  @Deprecated\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> load(@Nullable URL url) {\n    return loadGeneric(url);\n  }\n\n  /**\n   * Returns a request to load the given byte array.\n   *\n   * <p>Note - by default loads for bytes are not cached in either the memory or the disk cache.\n   *\n   * @param model the data to load.\n   * @see #load(Object)\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> load(@Nullable byte[] model) {\n    RequestBuilder<TranscodeType> result = loadGeneric(model);\n    if (!result.isDiskCacheStrategySet()) {\n      result = result.apply(diskCacheStrategyOf(DiskCacheStrategy.NONE));\n    }\n    if (!result.isSkipMemoryCacheSet()) {\n      result = result.apply(skipMemoryCacheOf(true /*skipMemoryCache*/));\n    }\n    return result;\n  }\n\n  /**\n   * Returns a copy of this request builder with all of the options put so far on this builder.\n   *\n   * <p>This method returns a \"deep\" copy in that all non-immutable arguments are copied such that\n   * changes to one builder will not affect the other builder. However, in addition to immutable\n   * arguments, the current model is not copied so changes to the model will affect both builders.\n   */\n  @SuppressWarnings({\n    // we don't want to throw to be user friendly\n    \"PMD.CloneThrowsCloneNotSupportedException\"\n  })\n  @CheckResult\n  @Override\n  public RequestBuilder<TranscodeType> clone() {\n    RequestBuilder<TranscodeType> result = super.clone();\n    result.transitionOptions = result.transitionOptions.clone();\n    if (result.requestListeners != null) {\n      result.requestListeners = new ArrayList<>(result.requestListeners);\n    }\n    if (result.thumbnailBuilder != null) {\n      result.thumbnailBuilder = result.thumbnailBuilder.clone();\n    }\n    if (result.errorBuilder != null) {\n      result.errorBuilder = result.errorBuilder.clone();\n    }\n    return result;\n  }\n\n  /**\n   * Set the target the resource will be loaded into.\n   *\n   * @param target The target to load the resource into.\n   * @return The given target.\n   * @see RequestManager#clear(Target)\n   */\n  @NonNull\n  public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {\n    return into(target, /* targetListener= */ null, Executors.mainThreadExecutor());\n  }\n\n  /**\n   * Set the target the resource will be loaded into; the callback will be set at the front of the\n   * queue.\n   *\n   * @param target The target to load the resource into.\n   * @return The given target.\n   * @see RequestManager#clear(Target)\n   */\n  @NonNull\n  public <Y extends Target<TranscodeType>> Y experimentalIntoFront(@NonNull Y target) {\n    return into(target, /* targetListener= */ null, Executors.mainThreadExecutorFront());\n  }\n\n  @NonNull\n  public <Y extends Target<TranscodeType>> Y into(\n      @NonNull Y target,\n      @Nullable RequestListener<TranscodeType> targetListener,\n      Executor callbackExecutor) {\n    return into(target, targetListener, /* options= */ this, callbackExecutor);\n  }\n\n  private <Y extends Target<TranscodeType>> Y into(\n      @NonNull Y target,\n      @Nullable RequestListener<TranscodeType> targetListener,\n      BaseRequestOptions<?> options,\n      Executor callbackExecutor) {\n    Preconditions.checkNotNull(target);\n    if (!isModelSet) {\n      throw new IllegalArgumentException(\"You must call #load() before calling #into()\");\n    }\n\n    Request request = buildRequest(target, targetListener, options, callbackExecutor);\n\n    Request previous = target.getRequest();\n    if (request.isEquivalentTo(previous)\n        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {\n      // If the request is completed, beginning again will ensure the result is re-delivered,\n      // triggering RequestListeners and Targets. If the request is failed, beginning again will\n      // restart the request, giving it another chance to complete. If the request is already\n      // running, we can let it continue running without interruption.\n      if (!Preconditions.checkNotNull(previous).isRunning()) {\n        // Use the previous request rather than the new one to allow for optimizations like skipping\n        // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions\n        // that are done in the individual Request.\n        previous.begin();\n      }\n      return target;\n    }\n\n    requestManager.clear(target);\n    target.setRequest(request);\n    requestManager.track(target, request);\n\n    return target;\n  }\n\n  // If the caller is using skipMemoryCache and the previous request is finished, calling begin on\n  // the previous request will complete from memory because it will just use the resource that had\n  // already been loaded. If the previous request isn't complete, we can wait for it to finish\n  // because the previous request must also be using skipMemoryCache for the requests to be\n  // equivalent. See #2663 for additional context.\n  private boolean isSkipMemoryCacheWithCompletePreviousRequest(\n      BaseRequestOptions<?> options, Request previous) {\n    return !options.isMemoryCacheable() && previous.isComplete();\n  }\n\n  /**\n   * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into\n   * the view, and frees any resources Glide may have previously loaded into the view so they may be\n   * reused.\n   *\n   * @see RequestManager#clear(Target)\n   * @param view The view to cancel previous loads for and load the new resource into.\n   * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link\n   *     ImageView}.\n   */\n  @NonNull\n  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {\n    Util.assertMainThread();\n    Preconditions.checkNotNull(view);\n\n    BaseRequestOptions<?> requestOptions = this;\n    if (!requestOptions.isTransformationSet()\n        && requestOptions.isTransformationAllowed()\n        && view.getScaleType() != null) {\n      // Clone in this method so that if we use this RequestBuilder to load into a View and then\n      // into a different target, we don't retain the transformation applied based on the previous\n      // View's scale type.\n      switch (view.getScaleType()) {\n        case CENTER_CROP:\n          requestOptions = requestOptions.clone().optionalCenterCrop();\n          break;\n        case CENTER_INSIDE:\n          requestOptions = requestOptions.clone().optionalCenterInside();\n          break;\n        case FIT_CENTER:\n        case FIT_START:\n        case FIT_END:\n          requestOptions = requestOptions.clone().optionalFitCenter();\n          break;\n        case FIT_XY:\n          requestOptions = requestOptions.clone().optionalCenterInside();\n          break;\n        case CENTER:\n        case MATRIX:\n        default:\n          // Do nothing.\n      }\n    }\n\n    return into(\n        glideContext.buildImageViewTarget(view, transcodeClass),\n        /* targetListener= */ null,\n        requestOptions,\n        Executors.mainThreadExecutor());\n  }\n\n  /**\n   * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into\n   * the view, and frees any resources Glide may have previously loaded into the view so they may be\n   * reused; the callback will be set at the front of the queue.\n   *\n   * @see RequestManager#clear(Target)\n   * @param view The view to cancel previous loads for and load the new resource into.\n   * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link\n   *     ImageView}.\n   */\n  @NonNull\n  public ViewTarget<ImageView, TranscodeType> experimentalIntoFront(@NonNull ImageView view) {\n    Util.assertMainThread();\n    Preconditions.checkNotNull(view);\n\n    BaseRequestOptions<?> requestOptions = this;\n    if (!requestOptions.isTransformationSet()\n        && requestOptions.isTransformationAllowed()\n        && view.getScaleType() != null) {\n      // Clone in this method so that if we use this RequestBuilder to load into a View and then\n      // into a different target, we don't retain the transformation applied based on the previous\n      // View's scale type.\n      switch (view.getScaleType()) {\n        case CENTER_CROP:\n          requestOptions = requestOptions.clone().optionalCenterCrop();\n          break;\n        case CENTER_INSIDE:\n          requestOptions = requestOptions.clone().optionalCenterInside();\n          break;\n        case FIT_CENTER:\n        case FIT_START:\n        case FIT_END:\n          requestOptions = requestOptions.clone().optionalFitCenter();\n          break;\n        case FIT_XY:\n          requestOptions = requestOptions.clone().optionalCenterInside();\n          break;\n        case CENTER:\n        case MATRIX:\n        default:\n          // Do nothing.\n      }\n    }\n\n    return into(\n        glideContext.buildImageViewTarget(view, transcodeClass),\n        /* targetListener= */ null,\n        requestOptions,\n        Executors.mainThreadExecutorFront());\n  }\n\n  /**\n   * Returns a future that can be used to do a blocking get on a background thread.\n   *\n   * @param width The desired width in pixels, or {@link Target#SIZE_ORIGINAL}. This will be\n   *     overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)} if\n   *     previously called.\n   * @param height The desired height in pixels, or {@link Target#SIZE_ORIGINAL}. This will be\n   *     overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)}} if\n   *     previously called).\n   * @see RequestManager#clear(Target)\n   * @deprecated Use {@link #submit(int, int)} instead.\n   */\n  @Deprecated\n  public FutureTarget<TranscodeType> into(int width, int height) {\n    return submit(width, height);\n  }\n\n  /**\n   * Returns a future that can be used to do a blocking get on a background thread.\n   *\n   * <p>This method defaults to {@link Target#SIZE_ORIGINAL} for the width and the height. However,\n   * since the width and height will be overridden by values passed to {@link\n   * RequestOptions#override(int, int)}, this method can be used whenever {@link RequestOptions}\n   * with override values are applied, or whenever you want to retrieve the image in its original\n   * size.\n   *\n   * @see #submit(int, int)\n   * @see #into(Target)\n   */\n  @NonNull\n  public FutureTarget<TranscodeType> submit() {\n    return submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);\n  }\n\n  /**\n   * Returns a future that can be used to do a blocking get on a background thread.\n   *\n   * @param width The desired width in pixels, or {@link Target#SIZE_ORIGINAL}. This will be\n   *     overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)} if\n   *     previously called.\n   * @param height The desired height in pixels, or {@link Target#SIZE_ORIGINAL}. This will be\n   *     overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)}} if\n   *     previously called).\n   */\n  @NonNull\n  public FutureTarget<TranscodeType> submit(int width, int height) {\n    final RequestFutureTarget<TranscodeType> target = new RequestFutureTarget<>(width, height);\n    return into(target, target, Executors.directExecutor());\n  }\n\n  /**\n   * Preloads the resource into the cache using the given width and height.\n   *\n   * <p>Pre-loading is useful for making sure that resources you are going to to want in the near\n   * future are available quickly.\n   *\n   * <p>Note - Any thumbnail request that does not complete before the primary request will be\n   * cancelled and may not be preloaded successfully. Cancellation of outstanding thumbnails after\n   * the primary request succeeds is a common behavior of all Glide requests. We do not try to\n   * prevent that behavior here. If you absolutely need all thumbnails to be preloaded individually,\n   * make separate preload() requests for each thumbnail (you can still combine them into one call\n   * when loading the image(s) into the UI in a subsequent request).\n   *\n   * @param width The desired width in pixels, or {@link Target#SIZE_ORIGINAL}. This will be\n   *     overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)} if\n   *     previously called.\n   * @param height The desired height in pixels, or {@link Target#SIZE_ORIGINAL}. This will be\n   *     overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)}} if\n   *     previously called).\n   * @return A {@link Target} that can be used to cancel the load via {@link\n   *     RequestManager#clear(Target)}.\n   * @see com.bumptech.glide.ListPreloader\n   */\n  @NonNull\n  public Target<TranscodeType> preload(int width, int height) {\n    final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(requestManager, width, height);\n    return into(target);\n  }\n\n  /**\n   * Preloads the resource into the cache using the given width and height; the callback will be set\n   * at the front of the queue.\n   *\n   * <p>Pre-loading is useful for making sure that resources you are going to to want in the near\n   * future are available quickly.\n   *\n   * <p>Note - Any thumbnail request that does not complete before the primary request will be\n   * cancelled and may not be preloaded successfully. Cancellation of outstanding thumbnails after\n   * the primary request succeeds is a common behavior of all Glide requests. We do not try to\n   * prevent that behavior here. If you absolutely need all thumbnails to be preloaded individually,\n   * make separate preload() requests for each thumbnail (you can still combine them into one call\n   * when loading the image(s) into the UI in a subsequent request).\n   *\n   * @param width The desired width in pixels, or {@link Target#SIZE_ORIGINAL}. This will be\n   *     overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)} if\n   *     previously called.\n   * @param height The desired height in pixels, or {@link Target#SIZE_ORIGINAL}. This will be\n   *     overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)}} if\n   *     previously called).\n   * @return A {@link Target} that can be used to cancel the load via {@link\n   *     RequestManager#clear(Target)}.\n   * @see com.bumptech.glide.ListPreloader\n   */\n  @NonNull\n  public Target<TranscodeType> experimentalPreloadFront(int width, int height) {\n    final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(requestManager, width, height);\n    return experimentalIntoFront(target);\n  }\n\n  /**\n   * Preloads the resource into the cache using {@link Target#SIZE_ORIGINAL} as the target width and\n   * height. Equivalent to calling {@link #preload(int, int)} with {@link Target#SIZE_ORIGINAL} as\n   * the width and height.\n   *\n   * @return A {@link Target} that can be used to cancel the load via {@link\n   *     RequestManager#clear(Target)}\n   * @see #preload(int, int)\n   */\n  @NonNull\n  public Target<TranscodeType> preload() {\n    return preload(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);\n  }\n\n  /**\n   * Loads the original unmodified data into the cache and calls the given Target with the cache\n   * File.\n   *\n   * @param target The Target that will receive the cache File when the load completes\n   * @param <Y> The type of Target.\n   * @return The given Target.\n   * @deprecated Use {@link RequestManager#downloadOnly()} and {@link #into(Target)}.\n   */\n  @Deprecated\n  @CheckResult\n  public <Y extends Target<File>> Y downloadOnly(@NonNull Y target) {\n    return getDownloadOnlyRequest().into(target);\n  }\n\n  /**\n   * Loads the original unmodified data into the cache and returns a {@link\n   * java.util.concurrent.Future} that can be used to retrieve the cache File containing the data.\n   *\n   * @param width The width in pixels to use to fetch the data.\n   * @param height The height in pixels to use to fetch the data.\n   * @return A {@link java.util.concurrent.Future} that can be used to retrieve the cache File\n   *     containing the data.\n   * @deprecated Use {@link RequestManager#downloadOnly()} and {@link #submit(int, int)}.\n   */\n  @Deprecated\n  @CheckResult\n  public FutureTarget<File> downloadOnly(int width, int height) {\n    return getDownloadOnlyRequest().submit(width, height);\n  }\n\n  @NonNull\n  @CheckResult\n  protected RequestBuilder<File> getDownloadOnlyRequest() {\n    return new RequestBuilder<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  @NonNull\n  private Priority getThumbnailPriority(@NonNull Priority current) {\n    switch (current) {\n      case LOW:\n        return Priority.NORMAL;\n      case NORMAL:\n        return Priority.HIGH;\n      case HIGH:\n      case IMMEDIATE:\n        return Priority.IMMEDIATE;\n      default:\n        throw new IllegalArgumentException(\"unknown priority: \" + getPriority());\n    }\n  }\n\n  private Request buildRequest(\n      Target<TranscodeType> target,\n      @Nullable RequestListener<TranscodeType> targetListener,\n      BaseRequestOptions<?> requestOptions,\n      Executor callbackExecutor) {\n    return buildRequestRecursive(\n        /* requestLock= */ new Object(),\n        target,\n        targetListener,\n        /* parentCoordinator= */ null,\n        transitionOptions,\n        requestOptions.getPriority(),\n        requestOptions.getOverrideWidth(),\n        requestOptions.getOverrideHeight(),\n        requestOptions,\n        callbackExecutor);\n  }\n\n  private Request buildRequestRecursive(\n      Object requestLock,\n      Target<TranscodeType> target,\n      @Nullable RequestListener<TranscodeType> targetListener,\n      @Nullable RequestCoordinator parentCoordinator,\n      TransitionOptions<?, ? super TranscodeType> transitionOptions,\n      Priority priority,\n      int overrideWidth,\n      int overrideHeight,\n      BaseRequestOptions<?> requestOptions,\n      Executor callbackExecutor) {\n\n    // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.\n    ErrorRequestCoordinator errorRequestCoordinator = null;\n    if (errorBuilder != null) {\n      errorRequestCoordinator = new ErrorRequestCoordinator(requestLock, parentCoordinator);\n      parentCoordinator = errorRequestCoordinator;\n    }\n\n    Request mainRequest =\n        buildThumbnailRequestRecursive(\n            requestLock,\n            target,\n            targetListener,\n            parentCoordinator,\n            transitionOptions,\n            priority,\n            overrideWidth,\n            overrideHeight,\n            requestOptions,\n            callbackExecutor);\n\n    if (errorRequestCoordinator == null) {\n      return mainRequest;\n    }\n\n    int errorOverrideWidth = errorBuilder.getOverrideWidth();\n    int errorOverrideHeight = errorBuilder.getOverrideHeight();\n    if (Util.isValidDimensions(overrideWidth, overrideHeight) && !errorBuilder.isValidOverride()) {\n      errorOverrideWidth = requestOptions.getOverrideWidth();\n      errorOverrideHeight = requestOptions.getOverrideHeight();\n    }\n\n    Request errorRequest =\n        errorBuilder.buildRequestRecursive(\n            requestLock,\n            target,\n            targetListener,\n            errorRequestCoordinator,\n            errorBuilder.transitionOptions,\n            errorBuilder.getPriority(),\n            errorOverrideWidth,\n            errorOverrideHeight,\n            errorBuilder,\n            callbackExecutor);\n    errorRequestCoordinator.setRequests(mainRequest, errorRequest);\n    return errorRequestCoordinator;\n  }\n\n  private Request buildThumbnailRequestRecursive(\n      Object requestLock,\n      Target<TranscodeType> target,\n      RequestListener<TranscodeType> targetListener,\n      @Nullable RequestCoordinator parentCoordinator,\n      TransitionOptions<?, ? super TranscodeType> transitionOptions,\n      Priority priority,\n      int overrideWidth,\n      int overrideHeight,\n      BaseRequestOptions<?> requestOptions,\n      Executor callbackExecutor) {\n    if (thumbnailBuilder != null) {\n      // Recursive case: contains a potentially recursive thumbnail request builder.\n      if (isThumbnailBuilt) {\n        throw new IllegalStateException(\n            \"You cannot use a request as both the main request and a \"\n                + \"thumbnail, consider using clone() on the request(s) passed to thumbnail()\");\n      }\n\n      TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions =\n          thumbnailBuilder.transitionOptions;\n\n      // Apply our transition by default to thumbnail requests but avoid overriding custom options\n      // that may have been applied on the thumbnail request explicitly.\n      if (thumbnailBuilder.isDefaultTransitionOptionsSet) {\n        thumbTransitionOptions = transitionOptions;\n      }\n\n      Priority thumbPriority =\n          thumbnailBuilder.isPrioritySet()\n              ? thumbnailBuilder.getPriority()\n              : getThumbnailPriority(priority);\n\n      int thumbOverrideWidth = thumbnailBuilder.getOverrideWidth();\n      int thumbOverrideHeight = thumbnailBuilder.getOverrideHeight();\n      if (Util.isValidDimensions(overrideWidth, overrideHeight)\n          && !thumbnailBuilder.isValidOverride()) {\n        thumbOverrideWidth = requestOptions.getOverrideWidth();\n        thumbOverrideHeight = requestOptions.getOverrideHeight();\n      }\n\n      ThumbnailRequestCoordinator coordinator =\n          new ThumbnailRequestCoordinator(requestLock, parentCoordinator);\n      Request fullRequest =\n          obtainRequest(\n              requestLock,\n              target,\n              targetListener,\n              requestOptions,\n              coordinator,\n              transitionOptions,\n              priority,\n              overrideWidth,\n              overrideHeight,\n              callbackExecutor);\n      isThumbnailBuilt = true;\n      // Recursively generate thumbnail requests.\n      Request thumbRequest =\n          thumbnailBuilder.buildRequestRecursive(\n              requestLock,\n              target,\n              targetListener,\n              coordinator,\n              thumbTransitionOptions,\n              thumbPriority,\n              thumbOverrideWidth,\n              thumbOverrideHeight,\n              thumbnailBuilder,\n              callbackExecutor);\n      isThumbnailBuilt = false;\n      coordinator.setRequests(fullRequest, thumbRequest);\n      return coordinator;\n    } else if (thumbSizeMultiplier != null) {\n      // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.\n      ThumbnailRequestCoordinator coordinator =\n          new ThumbnailRequestCoordinator(requestLock, parentCoordinator);\n      Request fullRequest =\n          obtainRequest(\n              requestLock,\n              target,\n              targetListener,\n              requestOptions,\n              coordinator,\n              transitionOptions,\n              priority,\n              overrideWidth,\n              overrideHeight,\n              callbackExecutor);\n      BaseRequestOptions<?> thumbnailOptions =\n          requestOptions.clone().sizeMultiplier(thumbSizeMultiplier);\n\n      Request thumbnailRequest =\n          obtainRequest(\n              requestLock,\n              target,\n              targetListener,\n              thumbnailOptions,\n              coordinator,\n              transitionOptions,\n              getThumbnailPriority(priority),\n              overrideWidth,\n              overrideHeight,\n              callbackExecutor);\n\n      coordinator.setRequests(fullRequest, thumbnailRequest);\n      return coordinator;\n    } else {\n      // Base case: no thumbnail.\n      return obtainRequest(\n          requestLock,\n          target,\n          targetListener,\n          requestOptions,\n          parentCoordinator,\n          transitionOptions,\n          priority,\n          overrideWidth,\n          overrideHeight,\n          callbackExecutor);\n    }\n  }\n\n  private Request obtainRequest(\n      Object requestLock,\n      Target<TranscodeType> target,\n      RequestListener<TranscodeType> targetListener,\n      BaseRequestOptions<?> requestOptions,\n      RequestCoordinator requestCoordinator,\n      TransitionOptions<?, ? super TranscodeType> transitionOptions,\n      Priority priority,\n      int overrideWidth,\n      int overrideHeight,\n      Executor callbackExecutor) {\n    return SingleRequest.obtain(\n        context,\n        glideContext,\n        requestLock,\n        model,\n        transcodeClass,\n        requestOptions,\n        overrideWidth,\n        overrideHeight,\n        priority,\n        target,\n        targetListener,\n        requestListeners,\n        requestCoordinator,\n        glideContext.getEngine(),\n        transitionOptions.getTransitionFactory(),\n        callbackExecutor);\n  }\n\n  Object getModel() {\n    return model;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof RequestBuilder<?>) {\n      RequestBuilder<?> that = (RequestBuilder<?>) o;\n      return super.equals(that)\n          && Objects.equals(transcodeClass, that.transcodeClass)\n          && transitionOptions.equals(that.transitionOptions)\n          && Objects.equals(model, that.model)\n          && Objects.equals(requestListeners, that.requestListeners)\n          && Objects.equals(thumbnailBuilder, that.thumbnailBuilder)\n          && Objects.equals(errorBuilder, that.errorBuilder)\n          && Objects.equals(thumbSizeMultiplier, that.thumbSizeMultiplier)\n          && isDefaultTransitionOptionsSet == that.isDefaultTransitionOptionsSet\n          && isModelSet == that.isModelSet;\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    int hashCode = super.hashCode();\n    hashCode = Util.hashCode(transcodeClass, hashCode);\n    hashCode = Util.hashCode(transitionOptions, hashCode);\n    hashCode = Util.hashCode(model, hashCode);\n    hashCode = Util.hashCode(requestListeners, hashCode);\n    hashCode = Util.hashCode(thumbnailBuilder, hashCode);\n    hashCode = Util.hashCode(errorBuilder, hashCode);\n    hashCode = Util.hashCode(thumbSizeMultiplier, hashCode);\n    hashCode = Util.hashCode(isDefaultTransitionOptionsSet, hashCode);\n    hashCode = Util.hashCode(isModelSet, hashCode);\n    return hashCode;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/RequestManager.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.request.RequestOptions.decodeTypeOf;\nimport static com.bumptech.glide.request.RequestOptions.diskCacheStrategyOf;\nimport static com.bumptech.glide.request.RequestOptions.skipMemoryCacheOf;\n\nimport android.content.ComponentCallbacks2;\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.view.View;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.manager.ConnectivityMonitor;\nimport com.bumptech.glide.manager.ConnectivityMonitorFactory;\nimport com.bumptech.glide.manager.Lifecycle;\nimport com.bumptech.glide.manager.LifecycleListener;\nimport com.bumptech.glide.manager.RequestManagerTreeNode;\nimport com.bumptech.glide.manager.RequestTracker;\nimport com.bumptech.glide.manager.TargetTracker;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.CustomViewTarget;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.io.File;\nimport java.net.URL;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * A class for managing and starting requests for Glide. Can use activity, fragment and connectivity\n * lifecycle events to intelligently stop, start, and restart requests. Retrieve either by\n * instantiating a new object, or to take advantage built in Activity and Fragment lifecycle\n * handling, use the static Glide.load methods with your Fragment or Activity.\n *\n * @see Glide#with(android.app.Activity)\n * @see Glide#with(androidx.fragment.app.FragmentActivity)\n * @see Glide#with(android.app.Fragment)\n * @see Glide#with(androidx.fragment.app.Fragment)\n * @see Glide#with(Context)\n */\npublic class RequestManager\n    implements ComponentCallbacks2, LifecycleListener, ModelTypes<RequestBuilder<Drawable>> {\n  private static final RequestOptions DECODE_TYPE_BITMAP = decodeTypeOf(Bitmap.class).lock();\n  private static final RequestOptions DECODE_TYPE_GIF = decodeTypeOf(GifDrawable.class).lock();\n  private static final RequestOptions DOWNLOAD_ONLY_OPTIONS =\n      diskCacheStrategyOf(DiskCacheStrategy.DATA).priority(Priority.LOW).skipMemoryCache(true);\n\n  protected final Glide glide;\n  protected final Context context;\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  final Lifecycle lifecycle;\n\n  @GuardedBy(\"this\")\n  private final RequestTracker requestTracker;\n\n  @GuardedBy(\"this\")\n  private final RequestManagerTreeNode treeNode;\n\n  @GuardedBy(\"this\")\n  private final TargetTracker targetTracker = new TargetTracker();\n\n  private final Runnable addSelfToLifecycle =\n      new Runnable() {\n        @Override\n        public void run() {\n          lifecycle.addListener(RequestManager.this);\n        }\n      };\n  private final ConnectivityMonitor connectivityMonitor;\n  // Adding default listeners should be much less common than starting new requests. We want\n  // some way of making sure that requests don't mutate our listeners without creating a new copy of\n  // the list each time a request is started.\n  private final CopyOnWriteArrayList<RequestListener<Object>> defaultRequestListeners;\n\n  @GuardedBy(\"this\")\n  private RequestOptions requestOptions;\n\n  private boolean pauseAllRequestsOnTrimMemoryModerate;\n\n  private boolean clearOnStop;\n\n  public RequestManager(\n      @NonNull Glide glide,\n      @NonNull Lifecycle lifecycle,\n      @NonNull RequestManagerTreeNode treeNode,\n      @NonNull Context context) {\n    this(\n        glide,\n        lifecycle,\n        treeNode,\n        new RequestTracker(),\n        glide.getConnectivityMonitorFactory(),\n        context);\n  }\n\n  // Our usage is safe here.\n  @SuppressWarnings(\"PMD.ConstructorCallsOverridableMethod\")\n  RequestManager(\n      Glide glide,\n      Lifecycle lifecycle,\n      RequestManagerTreeNode treeNode,\n      RequestTracker requestTracker,\n      ConnectivityMonitorFactory factory,\n      Context context) {\n    this.glide = glide;\n    this.lifecycle = lifecycle;\n    this.treeNode = treeNode;\n    this.requestTracker = requestTracker;\n    this.context = context;\n\n    connectivityMonitor =\n        factory.build(\n            context.getApplicationContext(),\n            new RequestManagerConnectivityListener(requestTracker));\n\n    // Order matters, this might be unregistered by teh listeners below, so we need to be sure to\n    // register first to prevent both assertions and memory leaks.\n    glide.registerRequestManager(this);\n\n    // If we're the application level request manager, we may be created on a background thread.\n    // In that case we cannot risk synchronously pausing or resuming requests, so we hack around the\n    // issue by delaying adding ourselves as a lifecycle listener by posting to the main thread.\n    // This should be entirely safe.\n    if (Util.isOnBackgroundThread()) {\n      Util.postOnUiThread(addSelfToLifecycle);\n    } else {\n      lifecycle.addListener(this);\n    }\n    lifecycle.addListener(connectivityMonitor);\n\n    defaultRequestListeners =\n        new CopyOnWriteArrayList<>(glide.getGlideContext().getDefaultRequestListeners());\n    setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());\n  }\n\n  protected synchronized void setRequestOptions(@NonNull RequestOptions toSet) {\n    requestOptions = toSet.clone().autoClone();\n  }\n\n  private synchronized void updateRequestOptions(@NonNull RequestOptions toUpdate) {\n    requestOptions = requestOptions.apply(toUpdate);\n  }\n\n  /**\n   * Updates the default {@link RequestOptions} for all loads started with this request manager with\n   * the given {@link RequestOptions}.\n   *\n   * <p>The {@link RequestOptions} provided here are applied on top of those provided via {@link\n   * GlideBuilder#setDefaultRequestOptions(RequestOptions)}. If there are conflicts, the options\n   * applied here will win. Note that this method does not mutate options provided to {@link\n   * GlideBuilder#setDefaultRequestOptions(RequestOptions)}.\n   *\n   * <p>Multiple sets of options can be applied. If there are conflicts the last {@link\n   * RequestOptions} applied will win.\n   *\n   * <p>The modified options will only be applied to loads started after this method is called.\n   *\n   * @see RequestBuilder#apply(BaseRequestOptions)\n   * @return This request manager.\n   */\n  @NonNull\n  public synchronized RequestManager applyDefaultRequestOptions(\n      @NonNull RequestOptions requestOptions) {\n    updateRequestOptions(requestOptions);\n    return this;\n  }\n\n  /**\n   * Replaces the default {@link RequestOptions} for all loads started with this request manager\n   * with the given {@link RequestOptions}.\n   *\n   * <p>The {@link RequestOptions} provided here replace those that have been previously provided\n   * via this method, {@link GlideBuilder#setDefaultRequestOptions(RequestOptions)}, and {@link\n   * #applyDefaultRequestOptions(RequestOptions)}.\n   *\n   * <p>Subsequent calls to {@link #applyDefaultRequestOptions(RequestOptions)} will not mutate the\n   * {@link RequestOptions} provided here. Instead the manager will create a clone of these options\n   * and mutate the clone.\n   *\n   * @see #applyDefaultRequestOptions(RequestOptions)\n   * @return This request manager.\n   */\n  @NonNull\n  public synchronized RequestManager setDefaultRequestOptions(\n      @NonNull RequestOptions requestOptions) {\n    setRequestOptions(requestOptions);\n    return this;\n  }\n\n  /**\n   * Clear all resources when onStop() from {@link LifecycleListener} is called.\n   *\n   * @return This request manager.\n   */\n  @NonNull\n  public synchronized RequestManager clearOnStop() {\n    clearOnStop = true;\n    return this;\n  }\n\n  /**\n   * Adds a default {@link RequestListener} that will be added to every request started with this\n   * {@link RequestManager}.\n   *\n   * <p>Multiple {@link RequestListener}s can be added here, in {@link RequestManager} scopes or to\n   * individual {@link RequestBuilder}s. {@link RequestListener}s are called in the order they're\n   * added. Even if an earlier {@link RequestListener} returns {@code true} from {@link\n   * RequestListener#onLoadFailed(GlideException, Object, Target, boolean)} or {@link\n   * RequestListener#onResourceReady(Object, Object, Target, DataSource, boolean)}, it will not\n   * prevent subsequent {@link RequestListener}s from being called.\n   *\n   * <p>Because Glide requests can be started for any number of individual resource types, any\n   * listener added here has to accept any generic resource type in {@link\n   * RequestListener#onResourceReady(Object, Object, Target, DataSource, boolean)}. If you must base\n   * the behavior of the listener on the resource type, you will need to use {@code instanceof} to\n   * do so. It's not safe to cast resource types without first checking with {@code instanceof}.\n   */\n  public RequestManager addDefaultRequestListener(RequestListener<Object> requestListener) {\n    defaultRequestListeners.add(requestListener);\n    return this;\n  }\n\n  /**\n   * If {@code true} then clear all in-progress and completed requests when the platform sends\n   * {@code onTrimMemory} with level = {@code TRIM_MEMORY_MODERATE}.\n   */\n  public void setPauseAllRequestsOnTrimMemoryModerate(boolean pauseAllOnTrim) {\n    pauseAllRequestsOnTrimMemoryModerate = pauseAllOnTrim;\n  }\n\n  /**\n   * Returns true if loads for this {@link RequestManager} are currently paused.\n   *\n   * @see #pauseRequests()\n   * @see #resumeRequests()\n   */\n  public synchronized boolean isPaused() {\n    return requestTracker.isPaused();\n  }\n\n  /**\n   * Cancels any in progress loads, but does not clear resources of completed loads.\n   *\n   * <p>Note #{@link #resumeRequests()} must be called for any requests made before or while the\n   * manager is paused to complete. RequestManagers attached to Fragments and Activities\n   * automatically resume onStart().\n   *\n   * @see #isPaused()\n   * @see #resumeRequests()\n   */\n  public synchronized void pauseRequests() {\n    requestTracker.pauseRequests();\n  }\n\n  /**\n   * Cancels any in progress loads and clears resources of completed loads.\n   *\n   * <p>Note #{@link #resumeRequests()} must be called for any requests made before or while the\n   * manager is paused to complete. RequestManagers attached to Fragments and Activities\n   * automatically resume onStart().\n   *\n   * <p>This will release the memory used by completed bitmaps but leaves them in any configured\n   * caches. When an #{@link android.app.Activity} receives #{@link\n   * android.app.Activity#onTrimMemory(int)} at a level of #{@link\n   * android.content.ComponentCallbacks2#TRIM_MEMORY_BACKGROUND} this is desirable in order to keep\n   * your process alive longer.\n   *\n   * @see #isPaused()\n   * @see #resumeRequests()\n   */\n  public synchronized void pauseAllRequests() {\n    requestTracker.pauseAllRequests();\n  }\n\n  /**\n   * Performs {@link #pauseAllRequests()} recursively for all managers that are contextually\n   * descendant to this manager based on the Activity/Fragment hierarchy.\n   *\n   * <p>Similar to {@link #pauseRequestsRecursive()} with the exception that it also clears\n   * resources of completed loads.\n   */\n  // Public API.\n  @SuppressWarnings({\"WeakerAccess\", \"unused\"})\n  public synchronized void pauseAllRequestsRecursive() {\n    pauseAllRequests();\n    for (RequestManager requestManager : treeNode.getDescendants()) {\n      requestManager.pauseAllRequests();\n    }\n  }\n\n  /**\n   * Performs {@link #pauseRequests()} recursively for all managers that are contextually descendant\n   * to this manager based on the Activity/Fragment hierarchy:\n   *\n   * <ul>\n   *   <li>When pausing on an Activity all attached fragments will also get paused.\n   *   <li>When pausing on an attached Fragment all descendant fragments will also get paused.\n   *   <li>When pausing on a detached Fragment or the application context only the current\n   *       RequestManager is paused.\n   * </ul>\n   *\n   * <p>Note, on pre-Jelly Bean MR1 calling pause on a Fragment will not cause child fragments to\n   * pause, in this case either call pause on the Activity or use a support Fragment.\n   */\n  // Public API.\n  @SuppressWarnings({\"WeakerAccess\", \"unused\"})\n  public synchronized void pauseRequestsRecursive() {\n    pauseRequests();\n    for (RequestManager requestManager : treeNode.getDescendants()) {\n      requestManager.pauseRequests();\n    }\n  }\n\n  /**\n   * Restarts any loads that have not yet completed.\n   *\n   * @see #isPaused()\n   * @see #pauseRequests()\n   */\n  public synchronized void resumeRequests() {\n    requestTracker.resumeRequests();\n  }\n\n  /**\n   * Performs {@link #resumeRequests()} recursively for all managers that are contextually\n   * descendant to this manager based on the Activity/Fragment hierarchy. The hierarchical semantics\n   * are identical as for {@link #pauseRequestsRecursive()}.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public synchronized void resumeRequestsRecursive() {\n    Util.assertMainThread();\n    resumeRequests();\n    for (RequestManager requestManager : treeNode.getDescendants()) {\n      requestManager.resumeRequests();\n    }\n  }\n\n  /**\n   * Lifecycle callback that registers for connectivity events (if the\n   * android.permission.ACCESS_NETWORK_STATE permission is present) and restarts failed or paused\n   * requests.\n   */\n  @Override\n  public synchronized void onStart() {\n    resumeRequests();\n    targetTracker.onStart();\n  }\n\n  /**\n   * Lifecycle callback that unregisters for connectivity events (if the\n   * android.permission.ACCESS_NETWORK_STATE permission is present) and pauses in progress loads and\n   * clears all resources if {@link #clearOnStop()} is called.\n   */\n  @Override\n  public synchronized void onStop() {\n    targetTracker.onStop();\n    if (clearOnStop) {\n      clearRequests();\n    } else {\n      pauseRequests();\n    }\n  }\n\n  /**\n   * Lifecycle callback that cancels all in progress requests and clears and recycles resources for\n   * all completed requests.\n   */\n  @Override\n  public synchronized void onDestroy() {\n    targetTracker.onDestroy();\n    clearRequests();\n    requestTracker.clearRequests();\n    lifecycle.removeListener(this);\n    lifecycle.removeListener(connectivityMonitor);\n    Util.removeCallbacksOnUiThread(addSelfToLifecycle);\n    glide.unregisterRequestManager(this);\n  }\n\n  /**\n   * Attempts to always load the resource as a {@link android.graphics.Bitmap}, even if it could\n   * actually be animated.\n   *\n   * @return A new request builder for loading a {@link android.graphics.Bitmap}\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<Bitmap> asBitmap() {\n    return as(Bitmap.class).apply(DECODE_TYPE_BITMAP);\n  }\n\n  /**\n   * Attempts to always load the resource as a {@link\n   * com.bumptech.glide.load.resource.gif.GifDrawable}.\n   *\n   * <p>If the underlying data is not a GIF, this will fail. As a result, this should only be used\n   * if the model represents an animated GIF and the caller wants to interact with the GifDrawable\n   * directly. Normally using just {@link #asDrawable()} is sufficient because it will determine\n   * whether or not the given data represents an animated GIF and return the appropriate {@link\n   * Drawable}, animated or not, automatically.\n   *\n   * @return A new request builder for loading a {@link\n   *     com.bumptech.glide.load.resource.gif.GifDrawable}.\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<GifDrawable> asGif() {\n    return as(GifDrawable.class).apply(DECODE_TYPE_GIF);\n  }\n\n  /**\n   * Attempts to always load the resource using any registered {@link\n   * com.bumptech.glide.load.ResourceDecoder}s that can decode any subclass of {@link Drawable}.\n   *\n   * <p>By default, may return either a {@link android.graphics.drawable.BitmapDrawable} or {@link\n   * GifDrawable}, but if additional decoders are registered for other {@link Drawable} subclasses,\n   * any of those subclasses may also be returned.\n   *\n   * @return A new request builder for loading a {@link Drawable}.\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<Drawable> asDrawable() {\n    return as(Drawable.class);\n  }\n\n  /**\n   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(Bitmap)}.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) {\n    return asDrawable().load(bitmap);\n  }\n\n  /**\n   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(Drawable)}.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<Drawable> load(@Nullable Drawable drawable) {\n    return asDrawable().load(drawable);\n  }\n\n  /**\n   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(String)}.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<Drawable> load(@Nullable String string) {\n    return asDrawable().load(string);\n  }\n\n  /**\n   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(Uri)}.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<Drawable> load(@Nullable Uri uri) {\n    return asDrawable().load(uri);\n  }\n\n  /**\n   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(File)}.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<Drawable> load(@Nullable File file) {\n    return asDrawable().load(file);\n  }\n\n  /**\n   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(Integer)}.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @SuppressWarnings(\"deprecation\")\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) {\n    return asDrawable().load(resourceId);\n  }\n\n  /**\n   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(URL)}.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @SuppressWarnings(\"deprecation\")\n  @CheckResult\n  @Override\n  @Deprecated\n  public RequestBuilder<Drawable> load(@Nullable URL url) {\n    return asDrawable().load(url);\n  }\n\n  /**\n   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(byte[])}.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<Drawable> load(@Nullable byte[] model) {\n    return asDrawable().load(model);\n  }\n\n  /**\n   * A helper method equivalent to calling {@link #asDrawable()} and then {@link\n   * RequestBuilder#load(Object)} with the given model.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @NonNull\n  @CheckResult\n  @Override\n  public RequestBuilder<Drawable> load(@Nullable Object model) {\n    return asDrawable().load(model);\n  }\n\n  /**\n   * Attempts always load the resource into the cache and return the {@link File} containing the\n   * cached source data.\n   *\n   * <p>This method is designed to work for remote data that is or will be cached using {@link\n   * com.bumptech.glide.load.engine.DiskCacheStrategy#DATA}. As a result, specifying a {@link\n   * com.bumptech.glide.load.engine.DiskCacheStrategy} on this request is generally not recommended.\n   *\n   * @return A new request builder for downloading content to cache and returning the cache File.\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<File> downloadOnly() {\n    return as(File.class).apply(DOWNLOAD_ONLY_OPTIONS);\n  }\n\n  /**\n   * A helper method equivalent to calling {@link #downloadOnly()} ()} and then {@link\n   * RequestBuilder#load(Object)} with the given model.\n   *\n   * @return A new request builder for loading a {@link Drawable} using the given model.\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<File> download(@Nullable Object model) {\n    return downloadOnly().load(model);\n  }\n\n  /**\n   * Attempts to always load a {@link File} containing the resource, either using a file path\n   * obtained from the media store (for local images/videos), or using Glide's disk cache (for\n   * remote images/videos).\n   *\n   * <p>For remote content, prefer {@link #downloadOnly()}.\n   *\n   * @return A new request builder for obtaining File paths to content.\n   */\n  @NonNull\n  @CheckResult\n  public RequestBuilder<File> asFile() {\n    return as(File.class).apply(skipMemoryCacheOf(true));\n  }\n\n  /**\n   * Attempts to load the resource using any registered {@link\n   * com.bumptech.glide.load.ResourceDecoder}s that can decode the given resource class or any\n   * subclass of the given resource class.\n   *\n   * @param resourceClass The resource to decode.\n   * @return A new request builder for loading the given resource class.\n   */\n  @NonNull\n  @CheckResult\n  public <ResourceType> RequestBuilder<ResourceType> as(\n      @NonNull Class<ResourceType> resourceClass) {\n    return new RequestBuilder<>(glide, this, resourceClass, context);\n  }\n\n  /**\n   * Cancel any pending loads Glide may have for the view and free any resources that may have been\n   * loaded for the view.\n   *\n   * <p>Note that this will only work if {@link View#setTag(Object)} is not called on this view\n   * outside of Glide.\n   *\n   * @param view The view to cancel loads and free resources for.\n   * @throws IllegalArgumentException if an object other than Glide's metadata is put as the view's\n   *     tag.\n   * @see #clear(Target)\n   */\n  public void clear(@NonNull View view) {\n    clear(new ClearTarget(view));\n  }\n\n  /**\n   * Cancel any pending loads Glide may have for the target and free any resources (such as {@link\n   * Bitmap}s) that may have been loaded for the target so they may be reused.\n   *\n   * @param target The Target to cancel loads for.\n   */\n  public void clear(@Nullable final Target<?> target) {\n    if (target == null) {\n      return;\n    }\n\n    untrackOrDelegate(target);\n  }\n\n  private void untrackOrDelegate(@NonNull Target<?> target) {\n    boolean isOwnedByUs = untrack(target);\n    // We'll end up here if the Target was cleared after the RequestManager that started the request\n    // is destroyed. That can happen for at least two reasons:\n    // 1. We call clear() on a background thread using something other than Application Context\n    // RequestManager.\n    // 2. The caller retains a reference to the RequestManager after the corresponding Activity or\n    // Fragment is destroyed, starts a load with it, and then clears that load with a different\n    // RequestManager. Callers seem especially likely to do this in retained Fragments (#2262).\n    //\n    // #1 is always an error. At best the caller is leaking memory briefly in something like an\n    // AsyncTask. At worst the caller is leaking an Activity or Fragment for a sustained period of\n    // time if they do something like reference the Activity RequestManager in a long lived\n    // background thread or task.\n    //\n    // #2 is always an error. Callers shouldn't be starting new loads using RequestManagers after\n    // the corresponding Activity or Fragment is destroyed because retaining any reference to the\n    // RequestManager leaks memory. It's possible that there's some brief period of time during or\n    // immediately after onDestroy where this is reasonable, but I can't think of why.\n    Request request = target.getRequest();\n    if (!isOwnedByUs && !glide.removeFromManagers(target) && request != null) {\n      target.setRequest(null);\n      request.clear();\n    }\n  }\n\n  synchronized boolean untrack(@NonNull Target<?> target) {\n    Request request = target.getRequest();\n    // If the Target doesn't have a request, it's already been cleared.\n    if (request == null) {\n      return true;\n    }\n\n    if (requestTracker.clearAndRemove(request)) {\n      targetTracker.untrack(target);\n      target.setRequest(null);\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  synchronized void track(@NonNull Target<?> target, @NonNull Request request) {\n    targetTracker.track(target);\n    requestTracker.runRequest(request);\n  }\n\n  List<RequestListener<Object>> getDefaultRequestListeners() {\n    return defaultRequestListeners;\n  }\n\n  synchronized RequestOptions getDefaultRequestOptions() {\n    return requestOptions;\n  }\n\n  @NonNull\n  <T> TransitionOptions<?, T> getDefaultTransitionOptions(Class<T> transcodeClass) {\n    return glide.getGlideContext().getDefaultTransitionOptions(transcodeClass);\n  }\n\n  @Override\n  public synchronized String toString() {\n    return super.toString() + \"{tracker=\" + requestTracker + \", treeNode=\" + treeNode + \"}\";\n  }\n\n  @Override\n  public void onTrimMemory(int level) {\n    if (level == TRIM_MEMORY_MODERATE && pauseAllRequestsOnTrimMemoryModerate) {\n      pauseAllRequestsRecursive();\n    }\n  }\n\n  @Override\n  public void onLowMemory() {\n    // Nothing to add conditionally. See Glide#onTrimMemory for unconditional behavior.\n  }\n\n  private synchronized void clearRequests() {\n    for (Target<?> target : targetTracker.getAll()) {\n      clear(target);\n    }\n    targetTracker.clear();\n  }\n\n  @Override\n  public void onConfigurationChanged(Configuration newConfig) {}\n\n  private class RequestManagerConnectivityListener\n      implements ConnectivityMonitor.ConnectivityListener {\n    @GuardedBy(\"RequestManager.this\")\n    private final RequestTracker requestTracker;\n\n    RequestManagerConnectivityListener(@NonNull RequestTracker requestTracker) {\n      this.requestTracker = requestTracker;\n    }\n\n    @Override\n    public void onConnectivityChanged(boolean isConnected) {\n      if (isConnected) {\n        synchronized (RequestManager.this) {\n          requestTracker.restartRequests();\n        }\n      }\n    }\n  }\n\n  private static class ClearTarget extends CustomViewTarget<View, Object> {\n\n    ClearTarget(@NonNull View view) {\n      super(view);\n    }\n\n    @Override\n    protected void onResourceCleared(@Nullable Drawable placeholder) {\n      // Do nothing, we don't retain a reference to our resource.\n    }\n\n    @Override\n    public void onLoadFailed(@Nullable Drawable errorDrawable) {\n      // Do nothing.\n    }\n\n    @Override\n    public void onResourceReady(\n        @NonNull Object resource, @Nullable Transition<? super Object> transition) {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/TransitionOptions.java",
    "content": "package com.bumptech.glide;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.request.transition.NoTransition;\nimport com.bumptech.glide.request.transition.TransitionFactory;\nimport com.bumptech.glide.request.transition.ViewAnimationFactory;\nimport com.bumptech.glide.request.transition.ViewPropertyAnimationFactory;\nimport com.bumptech.glide.request.transition.ViewPropertyTransition;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Util;\n\n/**\n * A base class for setting a transition to use on a resource when a load completes.\n *\n * <p>Note: Implementations must implement equals/hashcode.\n *\n * @param <CHILD> The implementation of this class to return to chain methods.\n * @param <TranscodeType> The type of resource that will be animated.\n */\npublic abstract class TransitionOptions<\n        CHILD extends TransitionOptions<CHILD, TranscodeType>, TranscodeType>\n    implements Cloneable {\n  private TransitionFactory<? super TranscodeType> transitionFactory = NoTransition.getFactory();\n\n  /**\n   * Removes any existing animation put on the builder. Will be overridden by subsequent calls that\n   * put an animation.\n   *\n   * @return This request builder.\n   */\n  @NonNull\n  public final CHILD dontTransition() {\n    return transition(NoTransition.getFactory());\n  }\n\n  /**\n   * Sets an {@link android.view.animation.Animation} to run on the wrapped target when an resource\n   * load finishes. Will only be run if the resource was loaded asynchronously (i.e. was not in the\n   * memory cache).\n   *\n   * @param viewAnimationId The resource id of the {@link android.view.animation.Animation} to use\n   *     as the transition.\n   * @return This request builder.\n   */\n  @NonNull\n  public final CHILD transition(int viewAnimationId) {\n    return transition(new ViewAnimationFactory<>(viewAnimationId));\n  }\n\n  /**\n   * Sets an animator to run a {@link android.view.ViewPropertyAnimator} on a view that the target\n   * may be wrapping when a resource load finishes. Will only be run if the load was loaded\n   * asynchronously (i.e. was not in the memory cache).\n   *\n   * @param animator The {@link com.bumptech.glide.request.transition.ViewPropertyTransition\n   *     .Animator} to run.\n   * @return This request builder.\n   */\n  @NonNull\n  public final CHILD transition(@NonNull ViewPropertyTransition.Animator animator) {\n    return transition(new ViewPropertyAnimationFactory<>(animator));\n  }\n\n  /**\n   * Uses the given {@link TransitionFactory} to build a {@link\n   * com.bumptech.glide.request.transition.Transition} for each request started with these {@code\n   * TransitionOptions}.\n   *\n   * @return This request builder.\n   */\n  @NonNull\n  public final CHILD transition(\n      @NonNull TransitionFactory<? super TranscodeType> transitionFactory) {\n    this.transitionFactory = Preconditions.checkNotNull(transitionFactory);\n    return self();\n  }\n\n  @SuppressWarnings({\n    // cast to CHILD is safe given the generic argument represents the object's runtime class\n    \"unchecked\",\n    // CHILD is the correct class name.\n    \"PMD.CloneMethodReturnTypeMustMatchClassName\",\n    // we don't want to throw to be user friendly\n    \"PMD.CloneThrowsCloneNotSupportedException\"\n  })\n  @Override\n  public final CHILD clone() {\n    try {\n      return (CHILD) super.clone();\n    } catch (CloneNotSupportedException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  final TransitionFactory<? super TranscodeType> getTransitionFactory() {\n    return transitionFactory;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private CHILD self() {\n    return (CHILD) this;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof TransitionOptions) {\n      TransitionOptions<?, ?> other = (TransitionOptions<?, ?>) o;\n      return Util.bothNullOrEqual(transitionFactory, other.transitionFactory);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return transitionFactory != null ? transitionFactory.hashCode() : 0;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/DataSource.java",
    "content": "package com.bumptech.glide.load;\n\n/** Indicates the origin of some retrieved data. */\npublic enum DataSource {\n  /**\n   * Indicates data was probably retrieved locally from the device, although it may have been\n   * obtained through a content provider that may have obtained the data from a remote source.\n   */\n  LOCAL,\n  /** Indicates data was retrieved from a remote source other than the device. */\n  REMOTE,\n  /** Indicates data was retrieved unmodified from the on device cache. */\n  DATA_DISK_CACHE,\n  /** Indicates data was retrieved from modified content in the on device cache. */\n  RESOURCE_DISK_CACHE,\n  /** Indicates data was retrieved from the in memory cache. */\n  MEMORY_CACHE,\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/DecodeFormat.java",
    "content": "package com.bumptech.glide.load;\n\n/**\n * Options for setting the value of {@link android.graphics.Bitmap#getConfig()} for {@link\n * android.graphics.Bitmap}s returned by {@link com.bumptech.glide.load.ResourceDecoder}s.\n *\n * <p>Note - In some cases it may not be possible to obey the requested setting, not all {@link\n * com.bumptech.glide.load.resource.bitmap.Downsampler}s support setting formats and certain images\n * may not be able to be loaded as certain configurations. Therefore this class represents a\n * preference rather than a requirement.\n */\npublic enum DecodeFormat {\n  /**\n   * Bitmaps returned by the {@link com.bumptech.glide.load.ResourceDecoder}. should return {@link\n   * android.graphics.Bitmap.Config#ARGB_8888} for {@link android.graphics.Bitmap#getConfig()} when\n   * possible.\n   *\n   * <p>On Android O+, this format will use ARGB_8888 only when it's not possible to use {@link\n   * android.graphics.Bitmap.Config#HARDWARE}. More information is available about hardware Bitmaps\n   * here: https://goo.gl/tn2A6k. If you need to disable hardware Bitmaps for a particular request,\n   * use {@link com.bumptech.glide.request.RequestOptions#disallowHardwareConfig()}.\n   *\n   * <p>GIF images decoded by {@link android.graphics.BitmapFactory} currently use an internal\n   * hidden format that is returned as null from {@link android.graphics.Bitmap#getConfig()}. Since\n   * we cannot force {@link android.graphics.BitmapFactory} to always return our desired config,\n   * this setting is a preference, not a promise.\n   */\n  PREFER_ARGB_8888,\n\n  /**\n   * Bitmaps decoded from image formats that support and/or use alpha (some types of PNGs, GIFs etc)\n   * should return {@link android.graphics.Bitmap.Config#ARGB_8888} for {@link\n   * android.graphics.Bitmap#getConfig()}. Bitmaps decoded from formats that don't support or use\n   * alpha should return {@link android.graphics.Bitmap.Config#RGB_565} for {@link\n   * android.graphics.Bitmap#getConfig()}.\n   *\n   * <p>On Android O+, this format will use RGB_565 only when it's not possible to use {@link\n   * android.graphics.Bitmap.Config#HARDWARE}.\n   */\n  PREFER_RGB_565;\n\n  /** The default value for DecodeFormat. */\n  public static final DecodeFormat DEFAULT = PREFER_ARGB_8888;\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/EncodeStrategy.java",
    "content": "package com.bumptech.glide.load;\n\n/**\n * Details how an {@link com.bumptech.glide.load.ResourceEncoder} will encode a resource to cache.\n */\npublic enum EncodeStrategy {\n  /**\n   * Writes the original unmodified data for the resource to disk, not include downsampling or\n   * transformations.\n   */\n  SOURCE,\n\n  /** Writes the decoded, downsampled and transformed data for the resource to disk. */\n  TRANSFORMED,\n\n  /** Will write no data. */\n  NONE,\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/Encoder.java",
    "content": "package com.bumptech.glide.load;\n\nimport androidx.annotation.NonNull;\nimport java.io.File;\n\n/**\n * An interface for writing data to some persistent data store (i.e. a local File cache).\n *\n * @param <T> The type of the data that will be written.\n */\npublic interface Encoder<T> {\n  /**\n   * Writes the given data to the given output stream and returns True if the write completed\n   * successfully and should be committed.\n   *\n   * @param data The data to write.\n   * @param file The file to write the data to.\n   * @param options The set of options to apply when encoding.\n   */\n  boolean encode(@NonNull T data, @NonNull File file, @NonNull Options options);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/HttpException.java",
    "content": "package com.bumptech.glide.load;\n\nimport androidx.annotation.Nullable;\nimport java.io.IOException;\n\n/**\n * Thrown when an http request fails.\n *\n * <p>Exposes the specific status code or {@link #UNKNOWN} via {@link #getStatusCode()} so users may\n * attempt to retry or otherwise uniformly handle certain types of errors regardless of the\n * underlying http library.\n */\n// Public API.\n@SuppressWarnings({\"WeakerAccess\", \"unused\"})\npublic final class HttpException extends IOException {\n  private static final long serialVersionUID = 1L;\n\n  public static final int UNKNOWN = -1;\n  private final int statusCode;\n\n  public HttpException(int statusCode) {\n    this(\"Http request failed\", statusCode);\n  }\n\n  /**\n   * @deprecated You should always include a status code, default to {@link #UNKNOWN} if you can't\n   *     come up with a reasonable one. This method will be removed in a future version.\n   */\n  @Deprecated\n  public HttpException(String message) {\n    this(message, UNKNOWN);\n  }\n\n  public HttpException(String message, int statusCode) {\n    this(message, statusCode, null /*cause*/);\n  }\n\n  public HttpException(String message, int statusCode, @Nullable Throwable cause) {\n    super(message + \", status code: \" + statusCode, cause);\n    this.statusCode = statusCode;\n  }\n\n  /**\n   * Returns the http status code, or {@link #UNKNOWN} if the request failed without providing a\n   * status code.\n   */\n  public int getStatusCode() {\n    return statusCode;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/ImageHeaderParser.java",
    "content": "package com.bumptech.glide.load;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\n\n/** Interface for the ImageHeaderParser. */\npublic interface ImageHeaderParser {\n  /**\n   * A constant indicating we were unable to parse the orientation from the image either because no\n   * exif segment containing orientation data existed, or because of an I/O error attempting to read\n   * the exif segment.\n   */\n  int UNKNOWN_ORIENTATION = -1;\n\n  /**\n   * The format of the image data including whether or not the image may include transparent pixels.\n   */\n  enum ImageType {\n    GIF(true),\n    JPEG(false),\n    RAW(false),\n    /** PNG type with alpha. */\n    PNG_A(true),\n    /** PNG type without alpha. */\n    PNG(false),\n    /** WebP type with alpha. */\n    WEBP_A(true),\n    /** WebP type without alpha. */\n    WEBP(false),\n    /** All animated webps. */\n    ANIMATED_WEBP(true),\n    /** Avif type (may contain alpha). */\n    AVIF(true),\n    /** Animated Avif type (may contain alpha). */\n    ANIMATED_AVIF(true),\n    /** Unrecognized type. */\n    UNKNOWN(false);\n\n    private final boolean hasAlpha;\n\n    ImageType(boolean hasAlpha) {\n      this.hasAlpha = hasAlpha;\n    }\n\n    public boolean hasAlpha() {\n      return hasAlpha;\n    }\n\n    public boolean isWebp() {\n      switch (this) {\n        case WEBP:\n        case WEBP_A:\n        case ANIMATED_WEBP:\n          return true;\n        default:\n          return false;\n      }\n    }\n  }\n\n  @NonNull\n  ImageType getType(@NonNull InputStream is) throws IOException;\n\n  @NonNull\n  ImageType getType(@NonNull ByteBuffer byteBuffer) throws IOException;\n\n  /**\n   * Parse the orientation from the image header. If it doesn't handle this image type (or this is\n   * not an image) it will return a default value rather than throwing an exception.\n   *\n   * @return The exif orientation if present or -1 if the header couldn't be parsed or doesn't\n   *     contain an orientation\n   */\n  int getOrientation(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool) throws IOException;\n\n  int getOrientation(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)\n      throws IOException;\n\n  /**\n   * Returns whether the {@link InputStream} has associated multi-picture-format (MPF) data. Only\n   * JPEGs have MPF data.\n   */\n  boolean hasJpegMpf(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool) throws IOException;\n\n  /**\n   * Returns whether the {@link ByteBuffer} has associated multi-picture-format (MPF) data. Only\n   * JPEGs have MPF data.\n   */\n  boolean hasJpegMpf(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)\n      throws IOException;\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/ImageHeaderParserUtils.java",
    "content": "package com.bumptech.glide.load;\n\nimport android.os.Build;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.util.List;\n\n/** Utilities for the ImageHeaderParser. */\npublic final class ImageHeaderParserUtils {\n  // 5MB. This is the max image header size we can handle, we preallocate a much smaller buffer but\n  // will resize up to this amount if necessary.\n  private static final int MARK_READ_LIMIT = 5 * 1024 * 1024;\n\n  private ImageHeaderParserUtils() {}\n\n  /** Returns the ImageType for the given InputStream. */\n  @NonNull\n  public static ImageType getType(\n      @NonNull List<ImageHeaderParser> parsers,\n      @Nullable InputStream is,\n      @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    if (is == null) {\n      return ImageType.UNKNOWN;\n    }\n\n    if (!is.markSupported()) {\n      is = new RecyclableBufferedInputStream(is, byteArrayPool);\n    }\n\n    is.mark(MARK_READ_LIMIT);\n    final InputStream finalIs = is;\n    return getTypeInternal(\n        parsers,\n        new TypeReader() {\n          @Override\n          public ImageType getTypeAndRewind(ImageHeaderParser parser) throws IOException {\n            try {\n              return parser.getType(finalIs);\n            } finally {\n              finalIs.reset();\n            }\n          }\n        });\n  }\n\n  /** Returns the ImageType for the given ByteBuffer. */\n  @NonNull\n  public static ImageType getType(\n      @NonNull List<ImageHeaderParser> parsers, @Nullable final ByteBuffer buffer)\n      throws IOException {\n    if (buffer == null) {\n      return ImageType.UNKNOWN;\n    }\n\n    return getTypeInternal(\n        parsers,\n        new TypeReader() {\n          @Override\n          public ImageType getTypeAndRewind(ImageHeaderParser parser) throws IOException {\n            try {\n              return parser.getType(buffer);\n            } finally {\n              ByteBufferUtil.rewind(buffer);\n            }\n          }\n        });\n  }\n\n  @NonNull\n  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n  public static ImageType getType(\n      @NonNull List<ImageHeaderParser> parsers,\n      @NonNull final ParcelFileDescriptorRewinder parcelFileDescriptorRewinder,\n      @NonNull final ArrayPool byteArrayPool)\n      throws IOException {\n    return getTypeInternal(\n        parsers,\n        new TypeReader() {\n          @Override\n          public ImageType getTypeAndRewind(ImageHeaderParser parser) throws IOException {\n            // Wrap the FileInputStream into a RecyclableBufferedInputStream to optimize I/O\n            // performance\n            RecyclableBufferedInputStream is = null;\n            try {\n              is =\n                  new RecyclableBufferedInputStream(\n                      new FileInputStream(\n                          parcelFileDescriptorRewinder.rewindAndGet().getFileDescriptor()),\n                      byteArrayPool);\n              return parser.getType(is);\n            } finally {\n              // If we close the stream, we'll close the file descriptor as well, so we can't do\n              // that. We do however want to make sure we release any buffers we used back to the\n              // pool so we call release instead of close.\n              if (is != null) {\n                is.release();\n              }\n              parcelFileDescriptorRewinder.rewindAndGet();\n            }\n          }\n        });\n  }\n\n  @NonNull\n  private static ImageType getTypeInternal(\n      @NonNull List<ImageHeaderParser> parsers, TypeReader reader) throws IOException {\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0, size = parsers.size(); i < size; i++) {\n      ImageHeaderParser parser = parsers.get(i);\n      ImageType type = reader.getTypeAndRewind(parser);\n      if (type != ImageType.UNKNOWN) {\n        return type;\n      }\n    }\n\n    return ImageType.UNKNOWN;\n  }\n\n  /**\n   * Returns the result from the first of {@code parsers} that returns something other than {@link\n   * ImageHeaderParser#UNKNOWN_ORIENTATION}.\n   *\n   * <p>If {@code buffer} is null, the parers list is empty, or none of the parsers returns a valid\n   * value, {@link ImageHeaderParser#UNKNOWN_ORIENTATION} is returned.\n   */\n  public static int getOrientation(\n      @NonNull List<ImageHeaderParser> parsers,\n      @Nullable final ByteBuffer buffer,\n      @NonNull final ArrayPool arrayPool)\n      throws IOException {\n    if (buffer == null) {\n      return ImageHeaderParser.UNKNOWN_ORIENTATION;\n    }\n\n    return getOrientationInternal(\n        parsers,\n        new OrientationReader() {\n          @Override\n          public int getOrientationAndRewind(ImageHeaderParser parser) throws IOException {\n            try {\n              return parser.getOrientation(buffer, arrayPool);\n            } finally {\n              ByteBufferUtil.rewind(buffer);\n            }\n          }\n        });\n  }\n\n  /** Returns the orientation for the given InputStream. */\n  public static int getOrientation(\n      @NonNull List<ImageHeaderParser> parsers,\n      @Nullable InputStream is,\n      @NonNull final ArrayPool byteArrayPool)\n      throws IOException {\n    if (is == null) {\n      return ImageHeaderParser.UNKNOWN_ORIENTATION;\n    }\n\n    if (!is.markSupported()) {\n      is = new RecyclableBufferedInputStream(is, byteArrayPool);\n    }\n\n    is.mark(MARK_READ_LIMIT);\n    final InputStream finalIs = is;\n    return getOrientationInternal(\n        parsers,\n        new OrientationReader() {\n          @Override\n          public int getOrientationAndRewind(ImageHeaderParser parser) throws IOException {\n            try {\n              return parser.getOrientation(finalIs, byteArrayPool);\n            } finally {\n              finalIs.reset();\n            }\n          }\n        });\n  }\n\n  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n  public static int getOrientation(\n      @NonNull List<ImageHeaderParser> parsers,\n      @NonNull final ParcelFileDescriptorRewinder parcelFileDescriptorRewinder,\n      @NonNull final ArrayPool byteArrayPool)\n      throws IOException {\n    return getOrientationInternal(\n        parsers,\n        new OrientationReader() {\n          @Override\n          public int getOrientationAndRewind(ImageHeaderParser parser) throws IOException {\n            // Wrap the FileInputStream into a RecyclableBufferedInputStream to optimize I/O\n            // performance\n            RecyclableBufferedInputStream is = null;\n            try {\n              is =\n                  new RecyclableBufferedInputStream(\n                      new FileInputStream(\n                          parcelFileDescriptorRewinder.rewindAndGet().getFileDescriptor()),\n                      byteArrayPool);\n              return parser.getOrientation(is, byteArrayPool);\n            } finally {\n              // If we close the stream, we'll close the file descriptor as well, so we can't do\n              // that. We do however want to make sure we release any buffers we used back to the\n              // pool so we call release instead of close.\n              if (is != null) {\n                is.release();\n              }\n              parcelFileDescriptorRewinder.rewindAndGet();\n            }\n          }\n        });\n  }\n\n  private static int getOrientationInternal(\n      @NonNull List<ImageHeaderParser> parsers, OrientationReader reader) throws IOException {\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0, size = parsers.size(); i < size; i++) {\n      ImageHeaderParser parser = parsers.get(i);\n      int orientation = reader.getOrientationAndRewind(parser);\n      if (orientation != ImageHeaderParser.UNKNOWN_ORIENTATION) {\n        return orientation;\n      }\n    }\n\n    return ImageHeaderParser.UNKNOWN_ORIENTATION;\n  }\n\n  /**\n   * Returns the result from the first of {@code parsers} that returns true when MPF is detected, if\n   * any..\n   *\n   * <p>If {@code buffer} is null, the parsers list is empty, or none of the parsers returns a valid\n   * value, false is returned.\n   */\n  public static boolean hasJpegMpf(\n      @NonNull List<ImageHeaderParser> parsers,\n      @Nullable final ByteBuffer buffer,\n      @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    if (buffer == null) {\n      return false;\n    }\n\n    return hasJpegMpfInternal(\n        parsers,\n        new JpegMpfReader() {\n          @Override\n          public boolean getHasJpegMpfAndRewind(ImageHeaderParser parser) throws IOException {\n            try {\n              return parser.hasJpegMpf(buffer, byteArrayPool);\n            } finally {\n              ByteBufferUtil.rewind(buffer);\n            }\n          }\n        });\n  }\n\n  /** Returns whether the given {@link InputStream} references MPF. */\n  public static boolean hasJpegMpf(\n      @NonNull List<ImageHeaderParser> parsers,\n      @Nullable InputStream is,\n      @NonNull final ArrayPool byteArrayPool)\n      throws IOException {\n    if (is == null) {\n      return false;\n    }\n\n    if (!is.markSupported()) {\n      is = new RecyclableBufferedInputStream(is, byteArrayPool);\n    }\n\n    is.mark(MARK_READ_LIMIT);\n    final InputStream finalIs = is;\n    return hasJpegMpfInternal(\n        parsers,\n        new JpegMpfReader() {\n          @Override\n          public boolean getHasJpegMpfAndRewind(ImageHeaderParser parser) throws IOException {\n            try {\n              return parser.hasJpegMpf(finalIs, byteArrayPool);\n            } finally {\n              finalIs.reset();\n            }\n          }\n        });\n  }\n\n  /** Returns whether the given {@link ParcelFileDescriptorRewinder} references MPF. */\n  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n  public static boolean hasJpegMpf(\n      @NonNull List<ImageHeaderParser> parsers,\n      @NonNull final ParcelFileDescriptorRewinder parcelFileDescriptorRewinder,\n      @NonNull final ArrayPool byteArrayPool)\n      throws IOException {\n    return hasJpegMpfInternal(\n        parsers,\n        new JpegMpfReader() {\n          @Override\n          public boolean getHasJpegMpfAndRewind(ImageHeaderParser parser) throws IOException {\n            // Wrap the FileInputStream into a RecyclableBufferedInputStream to optimize I/O\n            // performance\n            RecyclableBufferedInputStream is = null;\n            try {\n              is =\n                  new RecyclableBufferedInputStream(\n                      new FileInputStream(\n                          parcelFileDescriptorRewinder.rewindAndGet().getFileDescriptor()),\n                      byteArrayPool);\n              return parser.hasJpegMpf(is, byteArrayPool);\n            } finally {\n              // If we close the stream, we'll close the file descriptor as well, so we can't do\n              // that. We do however want to make sure we release any buffers we used back to the\n              // pool so we call release instead of close.\n              if (is != null) {\n                is.release();\n              }\n              parcelFileDescriptorRewinder.rewindAndGet();\n            }\n          }\n        });\n  }\n\n  private static boolean hasJpegMpfInternal(\n      @NonNull List<ImageHeaderParser> parsers, JpegMpfReader reader) throws IOException {\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0, size = parsers.size(); i < size; i++) {\n      ImageHeaderParser parser = parsers.get(i);\n      if (reader.getHasJpegMpfAndRewind(parser)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  private interface TypeReader {\n    ImageType getTypeAndRewind(ImageHeaderParser parser) throws IOException;\n  }\n\n  private interface OrientationReader {\n    int getOrientationAndRewind(ImageHeaderParser parser) throws IOException;\n  }\n\n  /** Reads JPEG multi-picture format (MPF) data. */\n  private interface JpegMpfReader {\n\n    /**\n     * Returns whether the image is JPEG and has MPF data.\n     *\n     * <p>The parser is guaranteed to be rewound upon termination of the method.\n     */\n    boolean getHasJpegMpfAndRewind(ImageHeaderParser parser) throws IOException;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/Key.java",
    "content": "package com.bumptech.glide.load;\n\nimport androidx.annotation.NonNull;\nimport java.nio.charset.Charset;\nimport java.security.MessageDigest;\n\n/**\n * An interface that uniquely identifies some put of data. Implementations must implement {@link\n * Object#equals(Object)} and {@link Object#hashCode()}. Implementations are generally expected to\n * add all uniquely identifying information used in in {@link java.lang.Object#equals(Object)}} and\n * {@link Object#hashCode()}} to the given {@link java.security.MessageDigest} in {@link\n * #updateDiskCacheKey(java.security.MessageDigest)}}, although this requirement is not as strict\n * for partial cache key signatures.\n */\npublic interface Key {\n  String STRING_CHARSET_NAME = \"UTF-8\";\n  Charset CHARSET = Charset.forName(STRING_CHARSET_NAME);\n\n  /**\n   * Adds all uniquely identifying information to the given digest.\n   *\n   * <p>Note - Using {@link java.security.MessageDigest#reset()} inside of this method will result\n   * in undefined behavior.\n   */\n  void updateDiskCacheKey(@NonNull MessageDigest messageDigest);\n\n  /**\n   * For caching to work correctly, implementations <em>must</em> implement this method and {@link\n   * #hashCode()}.\n   */\n  @Override\n  boolean equals(Object o);\n\n  /**\n   * For caching to work correctly, implementations <em>must</em> implement this method and {@link\n   * #equals(Object)}.\n   */\n  @Override\n  int hashCode();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/MultiTransformation.java",
    "content": "package com.bumptech.glide.load;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.security.MessageDigest;\nimport java.util.Arrays;\nimport java.util.Collection;\n\n/**\n * A transformation that applies one or more transformations in iteration order to a resource.\n *\n * @param <T> The type of {@link com.bumptech.glide.load.engine.Resource} that will be transformed.\n */\npublic class MultiTransformation<T> implements Transformation<T> {\n  private final Collection<? extends Transformation<T>> transformations;\n\n  @SafeVarargs\n  @SuppressWarnings(\"varargs\")\n  public MultiTransformation(@NonNull Transformation<T>... transformations) {\n    if (transformations.length == 0) {\n      throw new IllegalArgumentException(\n          \"MultiTransformation must contain at least one Transformation\");\n    }\n    this.transformations = Arrays.asList(transformations);\n  }\n\n  public MultiTransformation(@NonNull Collection<? extends Transformation<T>> transformationList) {\n    if (transformationList.isEmpty()) {\n      throw new IllegalArgumentException(\n          \"MultiTransformation must contain at least one Transformation\");\n    }\n    this.transformations = transformationList;\n  }\n\n  @NonNull\n  @Override\n  public Resource<T> transform(\n      @NonNull Context context, @NonNull Resource<T> resource, int outWidth, int outHeight) {\n    Resource<T> previous = resource;\n\n    for (Transformation<T> transformation : transformations) {\n      Resource<T> transformed = transformation.transform(context, previous, outWidth, outHeight);\n      if (previous != null && !previous.equals(resource) && !previous.equals(transformed)) {\n        previous.recycle();\n      }\n      previous = transformed;\n    }\n    return previous;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof MultiTransformation) {\n      MultiTransformation<?> other = (MultiTransformation<?>) o;\n      return transformations.equals(other.transformations);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return transformations.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    for (Transformation<T> transformation : transformations) {\n      transformation.updateDiskCacheKey(messageDigest);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/Option.java",
    "content": "package com.bumptech.glide.load;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.util.Preconditions;\nimport java.security.MessageDigest;\n\n/**\n * Defines available component (decoders, encoders, model loaders etc.) options with optional\n * default values and the ability to affect the resource disk cache key used by {@link\n * com.bumptech.glide.load.engine.DiskCacheStrategy#RESOURCE}.\n *\n * <p>Implementations must either be unique (usually declared as static final variables), or\n * implement {@link #equals(Object)} and {@link #hashCode()}.\n *\n * <p>Implementations can implement {@link #update(Object, MessageDigest)} to make sure that the\n * disk cache key includes the specific option set.\n *\n * @param <T> The type of the option ({@link Integer}, {@link\n *     android.graphics.Bitmap.CompressFormat} etc.), must implement {@link #equals(Object)} and\n *     {@link #hashCode()}.\n */\npublic final class Option<T> {\n  private static final CacheKeyUpdater<Object> EMPTY_UPDATER =\n      new CacheKeyUpdater<Object>() {\n        @Override\n        public void update(\n            @NonNull byte[] keyBytes, @NonNull Object value, @NonNull MessageDigest messageDigest) {\n          // Do nothing.\n        }\n      };\n\n  private final T defaultValue;\n  private final CacheKeyUpdater<T> cacheKeyUpdater;\n  private final String key;\n  private volatile byte[] keyBytes;\n\n  /**\n   * Returns a new {@link Option} that does not affect disk cache keys with a {@code null} default\n   * value.\n   *\n   * @param key A unique package prefixed {@link String} that identifies this option (must be stable\n   *     across builds, so {@link Class#getName()} should <em>not</em> be used).\n   */\n  @NonNull\n  public static <T> Option<T> memory(@NonNull String key) {\n    return new Option<>(key, null, Option.<T>emptyUpdater());\n  }\n\n  /**\n   * Returns a new {@link Option} that does not affect disk cache keys with the given value as the\n   * default value.\n   *\n   * @param key A unique package prefixed {@link String} that identifies this option (must be stable\n   *     across builds, so {@link Class#getName()} should <em>not</em> be used).\n   */\n  @NonNull\n  public static <T> Option<T> memory(@NonNull String key, @NonNull T defaultValue) {\n    return new Option<>(key, defaultValue, Option.<T>emptyUpdater());\n  }\n\n  /**\n   * Returns a new {@link Option} that uses the given {@link\n   * com.bumptech.glide.load.Option.CacheKeyUpdater} to update disk cache keys.\n   *\n   * @param key A unique package prefixed {@link String} that identifies this option (must be stable\n   *     across builds, so {@link Class#getName()} should <em>not</em> be used).\n   */\n  @NonNull\n  public static <T> Option<T> disk(\n      @NonNull String key, @NonNull CacheKeyUpdater<T> cacheKeyUpdater) {\n    return new Option<>(key, null, cacheKeyUpdater);\n  }\n\n  /**\n   * Returns a new {@link Option} that uses the given {@link\n   * com.bumptech.glide.load.Option.CacheKeyUpdater} to update disk cache keys and provides the\n   * given value as the default value.\n   *\n   * @param key A unique package prefixed {@link String} that identifies this option (must be stable\n   *     across builds, so {@link Class#getName()} should <em>not</em> be used).\n   */\n  @NonNull\n  public static <T> Option<T> disk(\n      @NonNull String key, @Nullable T defaultValue, @NonNull CacheKeyUpdater<T> cacheKeyUpdater) {\n    return new Option<>(key, defaultValue, cacheKeyUpdater);\n  }\n\n  private Option(\n      @NonNull String key, @Nullable T defaultValue, @NonNull CacheKeyUpdater<T> cacheKeyUpdater) {\n    this.key = Preconditions.checkNotEmpty(key);\n    this.defaultValue = defaultValue;\n    this.cacheKeyUpdater = Preconditions.checkNotNull(cacheKeyUpdater);\n  }\n\n  /** Returns a reasonable default to use if no other value is set, or {@code null}. */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @Nullable\n  public T getDefaultValue() {\n    return defaultValue;\n  }\n\n  /**\n   * Updates the given {@link MessageDigest} used to construct a cache key with the given value\n   * using the {@link com.bumptech.glide.load.Option.CacheKeyUpdater} optionally provided in the\n   * constructor.\n   */\n  public void update(@NonNull T value, @NonNull MessageDigest messageDigest) {\n    cacheKeyUpdater.update(getKeyBytes(), value, messageDigest);\n  }\n\n  @NonNull\n  private byte[] getKeyBytes() {\n    if (keyBytes == null) {\n      keyBytes = key.getBytes(Key.CHARSET);\n    }\n    return keyBytes;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof Option) {\n      Option<?> other = (Option<?>) o;\n      return key.equals(other.key);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return key.hashCode();\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  private static <T> CacheKeyUpdater<T> emptyUpdater() {\n    return (CacheKeyUpdater<T>) EMPTY_UPDATER;\n  }\n\n  @Override\n  public String toString() {\n    return \"Option{\" + \"key='\" + key + '\\'' + '}';\n  }\n\n  /**\n   * An interface that updates a {@link MessageDigest} with the given value as part of a process to\n   * generate a disk cache key.\n   *\n   * @param <T> The type of the option.\n   */\n  public interface CacheKeyUpdater<T> {\n    /**\n     * Updates the given {@link MessageDigest} with the bytes of the given key (to avoid incidental\n     * value collisions when values are not particularly unique) and value.\n     *\n     * <p>If your {@link Option} shouldn't affect the disk cache key, you should not implement this\n     * class and use {@link Option#memory(String)} or {@link Option#memory(String, Object)} instead.\n     *\n     * @param keyBytes The bytes of the {@link String} used as the key for this particular {@link\n     *     Option}. Should be added to the {@code messageDigest} using {@link\n     *     MessageDigest#update(byte[])} by all implementations if the digest is updated with the\n     *     given {@code value} parameter.\n     * @param value The value of of this particular option. Typically you should convert the value\n     *     to a byte array using some stable mechanism and then call {@link\n     *     MessageDigest#update(byte[])} to update the given digest.\n     */\n    void update(@NonNull byte[] keyBytes, @NonNull T value, @NonNull MessageDigest messageDigest);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/Options.java",
    "content": "package com.bumptech.glide.load;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArrayMap;\nimport androidx.collection.SimpleArrayMap;\nimport com.bumptech.glide.util.CachedHashCodeArrayMap;\nimport java.security.MessageDigest;\n\n/** A set of {@link Option Options} to apply to in memory and disk cache keys. */\npublic final class Options implements Key {\n  private final ArrayMap<Option<?>, Object> values = new CachedHashCodeArrayMap<>();\n\n  public void putAll(@NonNull Options other) {\n    values.putAll((SimpleArrayMap<Option<?>, Object>) other.values);\n  }\n\n  @NonNull\n  public <T> Options set(@NonNull Option<T> option, @NonNull T value) {\n    values.put(option, value);\n    return this;\n  }\n\n  // TODO(b/234614365): Expand usage of this method in BaseRequestOptions so that it's usable for\n  // other options.\n  public Options remove(@NonNull Option<?> option) {\n    values.remove(option);\n    return this;\n  }\n\n  @Nullable\n  @SuppressWarnings(\"unchecked\")\n  public <T> T get(@NonNull Option<T> option) {\n    return values.containsKey(option) ? (T) values.get(option) : option.getDefaultValue();\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof Options) {\n      Options other = (Options) o;\n      return values.equals(other.values);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return values.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    for (int i = 0; i < values.size(); i++) {\n      Option<?> key = values.keyAt(i);\n      Object value = values.valueAt(i);\n      updateDiskCacheKey(key, value, messageDigest);\n    }\n  }\n\n  @Override\n  public String toString() {\n    return \"Options{\" + \"values=\" + values + '}';\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static <T> void updateDiskCacheKey(\n      @NonNull Option<T> option, @NonNull Object value, @NonNull MessageDigest md) {\n    option.update((T) value, md);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/PreferredColorSpace.java",
    "content": "package com.bumptech.glide.load;\n\n/**\n * Glide's supported handling of color spaces on Android O+, defaults to null.\n *\n * <p>On Android O, Glide will always request SRGB and will ignore this option if set. A bug on\n * Android O prevents P3 images from being compressed correctly and can result in color distortion.\n * We may eventually work around this in Glide if sufficient demand arises, but doing so will\n * require a memory intensive conversion to SRGB prior to compressing Bitmaps to Glide's disk cache.\n * This work around would also work only for Glide's compression, not for any compression that a\n * caller performs on a Bitmap returned by Glide.\n *\n * <p>On Android P+, Glide supports SRGB and display P3. However, if display p3 is requested, we\n * will still decode to SRGB unless {@link android.graphics.BitmapFactory.Options#outColorSpace} is\n * also {@link android.graphics.ColorSpace.Named#DISPLAY_P3}. Preferring P3 for SRGB images adds\n * unnecessary CPU work to convert back and forth between the color spaces at decode time.\n *\n * <p>Using {@link #DISPLAY_P3} is wasteful if either the screen or the renderer do not support P3.\n * Currently Glide does not attempt to detect whether or not this support is present. Do not use\n * {@link #DISPLAY_P3} thinking that you're going to get higher quality by default. Only use {@link\n * #DISPLAY_P3} if you're confident you understand color spaces, your application is working with a\n * display that supports wide gamut and you've set the appropriate options to render wide gamut\n * colors. If you've missed one or more of these steps, {@link #DISPLAY_P3} can lead to poor color\n * quality and washed out looking images. When in doubt, always use {@link #SRGB}, which is Glide's\n * default.\n *\n * <p>As with {@link DecodeFormat} we cannot directly set color spaces, we can only suggest to the\n * framework which one we want. Setting one of these values is not a guarantee that any returned\n * Bitmap will actually use the requested color space.\n */\npublic enum PreferredColorSpace {\n  /** Prefers to decode images using {@link android.graphics.ColorSpace.Named#SRGB}. */\n  SRGB,\n  /** Prefers to decode images using {@link android.graphics.ColorSpace.Named#DISPLAY_P3}. */\n  DISPLAY_P3,\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/ResourceDecoder.java",
    "content": "package com.bumptech.glide.load;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.io.IOException;\n\n/**\n * An interface for decoding resources.\n *\n * @param <T> The type the resource will be decoded from (File, InputStream etc).\n * @param <Z> The type of the decoded resource (Bitmap, Drawable etc).\n */\npublic interface ResourceDecoder<T, Z> {\n\n  /**\n   * Returns {@code true} if this decoder is capable of decoding the given source with the given\n   * options, and {@code false} otherwise.\n   *\n   * <p>Decoders should make a best effort attempt to quickly determine if they are likely to be\n   * able to decode data, but should not attempt to completely read the given data. A typical\n   * implementation would check the file headers verify they match content the decoder expects to\n   * handle (i.e. a GIF decoder should verify that the image contains the GIF header block.\n   *\n   * <p>Decoders that return {@code true} from {@code handles} may still return {@code null} from\n   * {@link #decode(Object, int, int, Options)} if the data is partial or formatted incorrectly.\n   */\n  boolean handles(@NonNull T source, @NonNull Options options) throws IOException;\n\n  /**\n   * Returns a decoded resource from the given data or null if no resource could be decoded.\n   *\n   * <p>The {@code source} is managed by the caller, there's no need to close it. The returned\n   * {@link Resource} will be {@link Resource#recycle() released} when the engine sees fit.\n   *\n   * <p>Note - The {@code width} and {@code height} arguments are hints only, there is no\n   * requirement that the decoded resource exactly match the given dimensions. A typical use case\n   * would be to use the target dimensions to determine how much to downsample Bitmaps by to avoid\n   * overly large allocations.\n   *\n   * @param source The data the resource should be decoded from.\n   * @param width The ideal width in pixels of the decoded resource, or {@link\n   *     com.bumptech.glide.request.target.Target#SIZE_ORIGINAL} to indicate the original resource\n   *     width.\n   * @param height The ideal height in pixels of the decoded resource, or {@link\n   *     com.bumptech.glide.request.target.Target#SIZE_ORIGINAL} to indicate the original resource\n   *     height.\n   * @param options A map of string keys to objects that may or may not contain options available to\n   *     this particular implementation. Implementations should not assume that any or all of their\n   *     option keys are present. However, implementations may assume that if one of their option\n   *     keys is present, it's value is non-null and is of the expected type.\n   * @throws IOException typically only if the {@code source} ({@link java.io.InputStream}, {@link\n   *     android.os.ParcelFileDescriptor} etc) throws while being read.\n   * @throws OutOfMemoryError is sometimes thrown if the the request produces an overly large result\n   *     due to some combination of source size, requested size, source format and requested format.\n   *     Callers do/must handle this error and implementations can throw this error.\n   * @throws RuntimeException is thrown by a variety of decoding libraries, including various\n   *     Android libraries. Callers do/must handle this error and implementations can throw this\n   *     exception or, preferably, more detailed subclasses.\n   */\n  @Nullable\n  Resource<Z> decode(@NonNull T source, int width, int height, @NonNull Options options)\n      throws IOException;\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/ResourceEncoder.java",
    "content": "package com.bumptech.glide.load;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.Resource;\n\n/**\n * An interface for writing data from a resource to some persistent data store (i.e. a local File\n * cache).\n *\n * @param <T> The type of the data contained by the resource.\n */\npublic interface ResourceEncoder<T> extends Encoder<Resource<T>> {\n  // specializing the generic arguments\n  @NonNull\n  EncodeStrategy getEncodeStrategy(@NonNull Options options);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/Transformation.java",
    "content": "package com.bumptech.glide.load;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.nio.charset.Charset;\nimport java.security.MessageDigest;\n\n/**\n * A class for performing an arbitrary transformation on a resource that implements {@link\n * #equals(Object)} and {@link #hashCode()}} to identify the transformation in the memory cache and\n * {@link #updateDiskCacheKey(java.security.MessageDigest)}} to identify the transformation in disk\n * caches.\n *\n * <p>Using the fully qualified class name as a static final {@link String} (not {@link\n * Class#getName()} to avoid proguard obfuscation) is an easy way to implement {@link\n * #updateDiskCacheKey(java.security.MessageDigest)}} correctly. If additional arguments are\n * required they can be passed in to the constructor of the {@code Transformation} and then used to\n * update the {@link java.security.MessageDigest} passed in to {@link\n * #updateDiskCacheKey(MessageDigest)}. If arguments are primitive types, they can typically easily\n * be serialized using {@link java.nio.ByteBuffer}. {@link String} types can be serialized with\n * {@link String#getBytes(Charset)} using the constant {@link #CHARSET}.\n *\n * <p>Implementations <em>must</em> implement {@link #equals(Object)} and {@link #hashCode()} for\n * memory caching to work correctly.\n *\n * @param <T> The type of the resource being transformed.\n */\npublic interface Transformation<T> extends Key {\n\n  /**\n   * Transforms the given resource and returns the transformed resource.\n   *\n   * <p>If the original resource object is not returned, the original resource will be recycled and\n   * it's internal resources may be reused. This means it is not safe to rely on the original\n   * resource or any internal state of the original resource in any new resource that is created.\n   * Usually this shouldn't occur, but if absolutely necessary either the original resource object\n   * can be returned with modified internal state, or the data in the original resource can be\n   * copied into the transformed resource.\n   *\n   * <p>If a Transformation is updated, {@link #equals(Object)}, {@link #hashCode()}, and {@link\n   * #updateDiskCacheKey(java.security.MessageDigest)} should all change. If you're using a simple\n   * String key an easy way to do this is to append a version number to your key. Failing to do so\n   * will mean users may see images loaded from cache that had the old version of the Transformation\n   * applied. Changing the return values of those methods will ensure that the cache key has changed\n   * and therefore that any cached resources will be re-generated using the updated Transformation.\n   *\n   * <p>During development you may need to either using {@link\n   * com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} or make sure {@link\n   * #updateDiskCacheKey(java.security.MessageDigest)} changes each time you make a change to the\n   * Transformation. Otherwise the resource you request may be loaded from disk cache and your\n   * Transformation may not be called.\n   *\n   * @param context The Application context\n   * @param resource The resource to transform.\n   * @param outWidth The width of the view or target the resource will be displayed in, or {@link\n   *     com.bumptech.glide.request.target.Target#SIZE_ORIGINAL} to indicate the original resource\n   *     width.\n   * @param outHeight The height of the view or target the resource will be displayed in, or {@link\n   *     com.bumptech.glide.request.target.Target#SIZE_ORIGINAL} to indicate the original resource\n   *     height.\n   * @return The transformed resource.\n   */\n  @NonNull\n  Resource<T> transform(\n      @NonNull Context context, @NonNull Resource<T> resource, int outWidth, int outHeight);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/AssetFileDescriptorLocalUriFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport android.content.ContentResolver;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport androidx.annotation.NonNull;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\n\n/** Fetches an {@link AssetFileDescriptor} for a local {@link android.net.Uri}. */\npublic final class AssetFileDescriptorLocalUriFetcher extends LocalUriFetcher<AssetFileDescriptor> {\n\n  public AssetFileDescriptorLocalUriFetcher(ContentResolver contentResolver, Uri uri) {\n    super(contentResolver, uri);\n  }\n\n  /**\n   * useMediaStoreApisIfAvailable is part of an experiment and the constructor can be removed in a\n   * future version.\n   */\n  public AssetFileDescriptorLocalUriFetcher(\n      ContentResolver contentResolver, Uri uri, boolean useMediaStoreApisIfAvailable) {\n    super(contentResolver, uri, useMediaStoreApisIfAvailable);\n  }\n\n  @Override\n  protected AssetFileDescriptor loadResource(Uri uri, ContentResolver contentResolver)\n      throws FileNotFoundException {\n    AssetFileDescriptor result = openAssetFileDescriptor(uri);\n    if (result == null) {\n      throw new FileNotFoundException(\"FileDescriptor is null for: \" + uri);\n    }\n    return result;\n  }\n\n  @Override\n  protected void close(AssetFileDescriptor data) throws IOException {\n    data.close();\n  }\n\n  @NonNull\n  @Override\n  public Class<AssetFileDescriptor> getDataClass() {\n    return AssetFileDescriptor.class;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/AssetPathFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport android.content.res.AssetManager;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport java.io.IOException;\n\n/**\n * An abstract class for obtaining data for an asset path using an {@link\n * android.content.res.AssetManager}.\n *\n * @param <T> The type of data obtained from the asset path (InputStream, FileDescriptor etc).\n */\npublic abstract class AssetPathFetcher<T> implements DataFetcher<T> {\n  private static final String TAG = \"AssetPathFetcher\";\n  private final String assetPath;\n  private final AssetManager assetManager;\n  private T data;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public AssetPathFetcher(AssetManager assetManager, String assetPath) {\n    this.assetManager = assetManager;\n    this.assetPath = assetPath;\n  }\n\n  @Override\n  public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback) {\n    try {\n      data = loadResource(assetManager, assetPath);\n      callback.onDataReady(data);\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to load data from asset manager\", e);\n      }\n      callback.onLoadFailed(e);\n    }\n  }\n\n  @Override\n  public void cleanup() {\n    if (data == null) {\n      return;\n    }\n    try {\n      close(data);\n    } catch (IOException e) {\n      // Ignored.\n    }\n  }\n\n  @Override\n  public void cancel() {\n    // Do nothing.\n  }\n\n  @NonNull\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.LOCAL;\n  }\n\n  /**\n   * Opens the given asset path with the given {@link android.content.res.AssetManager} and returns\n   * the concrete data type returned by the AssetManager.\n   *\n   * @param assetManager An AssetManager to use to open the given path.\n   * @param path A string path pointing to a resource in assets to open.\n   */\n  protected abstract T loadResource(AssetManager assetManager, String path) throws IOException;\n\n  /**\n   * Closes the concrete data type if necessary.\n   *\n   * @param data The data to close.\n   */\n  protected abstract void close(T data) throws IOException;\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/BufferedOutputStream.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * An {@link OutputStream} implementation that recycles and re-uses {@code byte[]}s using the\n * provided {@link ArrayPool}.\n */\npublic final class BufferedOutputStream extends OutputStream {\n  @NonNull private final OutputStream out;\n  private byte[] buffer;\n  private ArrayPool arrayPool;\n  private int index;\n\n  public BufferedOutputStream(@NonNull OutputStream out, @NonNull ArrayPool arrayPool) {\n    this(out, arrayPool, ArrayPool.STANDARD_BUFFER_SIZE_BYTES);\n  }\n\n  @VisibleForTesting\n  BufferedOutputStream(@NonNull OutputStream out, ArrayPool arrayPool, int bufferSize) {\n    this.out = out;\n    this.arrayPool = arrayPool;\n    buffer = arrayPool.get(bufferSize, byte[].class);\n  }\n\n  @Override\n  public void write(int b) throws IOException {\n    buffer[index++] = (byte) b;\n    maybeFlushBuffer();\n  }\n\n  @Override\n  public void write(@NonNull byte[] b) throws IOException {\n    write(b, 0, b.length);\n  }\n\n  @Override\n  public void write(@NonNull byte[] b, int initialOffset, int length) throws IOException {\n    int writtenSoFar = 0;\n    do {\n      int remainingToWrite = length - writtenSoFar;\n      int currentOffset = initialOffset + writtenSoFar;\n      // If we still need to write at least the buffer size worth of bytes, we might as well do so\n      // directly and avoid the overhead of copying to the buffer first.\n      if (index == 0 && remainingToWrite >= buffer.length) {\n        out.write(b, currentOffset, remainingToWrite);\n        return;\n      }\n\n      int remainingSpaceInBuffer = buffer.length - index;\n      int totalBytesToWriteToBuffer = Math.min(remainingToWrite, remainingSpaceInBuffer);\n\n      System.arraycopy(b, currentOffset, buffer, index, totalBytesToWriteToBuffer);\n\n      index += totalBytesToWriteToBuffer;\n      writtenSoFar += totalBytesToWriteToBuffer;\n\n      maybeFlushBuffer();\n    } while (writtenSoFar < length);\n  }\n\n  @Override\n  public void flush() throws IOException {\n    flushBuffer();\n    out.flush();\n  }\n\n  private void flushBuffer() throws IOException {\n    if (index > 0) {\n      out.write(buffer, 0, index);\n      index = 0;\n    }\n  }\n\n  private void maybeFlushBuffer() throws IOException {\n    if (index == buffer.length) {\n      flushBuffer();\n    }\n  }\n\n  @Override\n  public void close() throws IOException {\n    try {\n      flush();\n    } finally {\n      out.close();\n    }\n    release();\n  }\n\n  private void release() {\n    if (buffer != null) {\n      arrayPool.put(buffer);\n      buffer = null;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/DataFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\n\n/**\n * Lazily retrieves data that can be used to load a resource.\n *\n * <p>A new instance is created per resource load by {@link\n * com.bumptech.glide.load.model.ModelLoader}. {@link #loadData(com.bumptech.glide.Priority,\n * com.bumptech.glide.load.data.DataFetcher.DataCallback)} may or may not be called for any given\n * load depending on whether or not the corresponding resource is cached. Cancel also may or may not\n * be called. If {@link #loadData(com.bumptech.glide.Priority,\n * com.bumptech.glide.load.data.DataFetcher.DataCallback)}} is called, then so {@link #cleanup()}\n * will be called.\n *\n * @param <T> The type of data to be loaded (InputStream, byte[], File etc).\n */\npublic interface DataFetcher<T> {\n\n  /**\n   * Callback that must be called when data has been loaded and is available, or when the load\n   * fails.\n   *\n   * @param <T> The type of data that will be loaded.\n   */\n  interface DataCallback<T> {\n\n    /**\n     * Called with the loaded data if the load succeeded, or with {@code null} if the load failed.\n     */\n    void onDataReady(@Nullable T data);\n\n    /**\n     * Called when the load fails.\n     *\n     * @param e a non-null {@link Exception} indicating why the load failed.\n     */\n    void onLoadFailed(@NonNull Exception e);\n  }\n\n  /**\n   * Fetch data from which a resource can be decoded.\n   *\n   * <p>This will always be called on background thread so it is safe to perform long running tasks\n   * here. Any third party libraries called must be thread safe (or move the work to another thread)\n   * since this method will be called from a thread in a {@link\n   * java.util.concurrent.ExecutorService} that may have more than one background thread. You\n   * <b>MUST</b> use the {@link DataCallback} once the request is complete.\n   *\n   * <p>You are free to move the fetch work to another thread and call the callback from there.\n   *\n   * <p>This method will only be called when the corresponding resource is not in the cache.\n   *\n   * <p>Note - this method will be run on a background thread so blocking I/O is safe.\n   *\n   * @param priority The priority with which the request should be completed.\n   * @param callback The callback to use when the request is complete\n   * @see #cleanup() where the data retuned will be cleaned up\n   */\n  void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);\n\n  /**\n   * Cleanup or recycle any resources used by this data fetcher. This method will be called in a\n   * finally block after the data provided by {@link #loadData(com.bumptech.glide.Priority,\n   * com.bumptech.glide.load.data.DataFetcher.DataCallback)} has been decoded by the {@link\n   * com.bumptech.glide.load.ResourceDecoder}.\n   *\n   * <p>Note - this method will be run on a background thread so blocking I/O is safe.\n   */\n  void cleanup();\n\n  /**\n   * A method that will be called when a load is no longer relevant and has been cancelled. This\n   * method does not need to guarantee that any in process loads do not finish. It also may be\n   * called before a load starts or after it finishes.\n   *\n   * <p>The best way to use this method is to cancel any loads that have not yet started, but allow\n   * those that are in process to finish since its we typically will want to display the same\n   * resource in a different view in the near future.\n   *\n   * <p>Note - this method will be run on the main thread so it should not perform blocking\n   * operations and should finish quickly.\n   */\n  void cancel();\n\n  /** Returns the class of the data this fetcher will attempt to obtain. */\n  @NonNull\n  Class<T> getDataClass();\n\n  /** Returns the {@link com.bumptech.glide.load.DataSource} this fetcher will return data from. */\n  @NonNull\n  DataSource getDataSource();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/DataRewinder.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport androidx.annotation.NonNull;\nimport java.io.IOException;\n\n/**\n * Responsible for rewinding a stream like data types.\n *\n * @param <T> The stream like data type that can be rewound.\n */\npublic interface DataRewinder<T> {\n\n  /**\n   * A factory interface for producing individual {@link\n   * com.bumptech.glide.load.data.DataRewinder}s.\n   *\n   * @param <T> The type of data that the {@link com.bumptech.glide.load.data.DataRewinder} will\n   *     wrap.\n   */\n  interface Factory<T> {\n    /** Returns a new {@link com.bumptech.glide.load.data.DataRewinder} wrapping the given data. */\n    @NonNull\n    DataRewinder<T> build(@NonNull T data);\n\n    /**\n     * Returns the class of data this factory can produce {@link\n     * com.bumptech.glide.load.data.DataRewinder}s for.\n     */\n    @NonNull\n    Class<T> getDataClass();\n  }\n\n  /**\n   * Rewinds the wrapped data back to the beginning and returns the re-wound data (or a wrapper for\n   * the re-wound data).\n   *\n   * @return An object pointing to the wrapped data.\n   */\n  @NonNull\n  T rewindAndGet() throws IOException;\n\n  /**\n   * Called when this rewinder is no longer needed and can be cleaned up.\n   *\n   * <p>The underlying data may still be in use and should not be closed or invalidated.\n   */\n  void cleanup();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/DataRewinderRegistry.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Stores a mapping of data class to {@link com.bumptech.glide.load.data.DataRewinder.Factory} and\n * allows registration of new types and factories.\n */\npublic class DataRewinderRegistry {\n  private final Map<Class<?>, DataRewinder.Factory<?>> rewinders = new HashMap<>();\n  private static final DataRewinder.Factory<?> DEFAULT_FACTORY =\n      new DataRewinder.Factory<Object>() {\n        @NonNull\n        @Override\n        public DataRewinder<Object> build(@NonNull Object data) {\n          return new DefaultRewinder(data);\n        }\n\n        @NonNull\n        @Override\n        public Class<Object> getDataClass() {\n          throw new UnsupportedOperationException(\"Not implemented\");\n        }\n      };\n\n  public synchronized void register(@NonNull DataRewinder.Factory<?> factory) {\n    rewinders.put(factory.getDataClass(), factory);\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  public synchronized <T> DataRewinder<T> build(@NonNull T data) {\n    Preconditions.checkNotNull(data);\n    DataRewinder.Factory<T> result = (DataRewinder.Factory<T>) rewinders.get(data.getClass());\n    if (result == null) {\n      for (DataRewinder.Factory<?> registeredFactory : rewinders.values()) {\n        if (registeredFactory.getDataClass().isAssignableFrom(data.getClass())) {\n          result = (DataRewinder.Factory<T>) registeredFactory;\n          break;\n        }\n      }\n    }\n\n    if (result == null) {\n      result = (DataRewinder.Factory<T>) DEFAULT_FACTORY;\n    }\n    return result.build(data);\n  }\n\n  private static final class DefaultRewinder implements DataRewinder<Object> {\n    private final Object data;\n\n    DefaultRewinder(@NonNull Object data) {\n      this.data = data;\n    }\n\n    @NonNull\n    @Override\n    public Object rewindAndGet() {\n      return data;\n    }\n\n    @Override\n    public void cleanup() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/ExifOrientationStream.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport androidx.annotation.NonNull;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Adds an exif segment with an orientation attribute to a wrapped {@link InputStream} containing\n * image data.\n *\n * <p>This class assumes that the wrapped stream contains an image format that can contain exif\n * information and performs no verification.\n */\npublic final class ExifOrientationStream extends FilterInputStream {\n  /** Allow two bytes for the file format. */\n  private static final int SEGMENT_START_POSITION = 2;\n\n  private static final byte[] EXIF_SEGMENT =\n      new byte[] {\n        /* segment start id. */\n        (byte) 0xFF,\n        /* segment type. */\n        (byte) 0xE1,\n        /* segmentLength. */\n        0x00,\n        (byte) 0x1C,\n        /* exif identifier. */\n        0x45,\n        0x78,\n        0x69,\n        0x66,\n        0x00,\n        0x00,\n        /* motorola byte order (big endian). */\n        (byte) 0x4D,\n        (byte) 0x4D,\n        /* filler? */\n        0x00,\n        0x00,\n        /* first id offset. */\n        0x00,\n        0x00,\n        0x00,\n        0x08,\n        /* tagCount. */\n        0x00,\n        0x01,\n        /* exif tag type. */\n        0x01,\n        0x12,\n        /* 2 byte format. */\n        0x00,\n        0x02,\n        /* component count. */\n        0x00,\n        0x00,\n        0x00,\n        0x01,\n        /* 2 byte orientation value, the first byte of which is always 0. */\n        0x00,\n      };\n  private static final int SEGMENT_LENGTH = EXIF_SEGMENT.length;\n  private static final int ORIENTATION_POSITION = SEGMENT_LENGTH + SEGMENT_START_POSITION;\n  private final byte orientation;\n  private int position;\n\n  public ExifOrientationStream(InputStream in, int orientation) {\n    super(in);\n    if (orientation < -1 || orientation > 8) {\n      throw new IllegalArgumentException(\"Cannot add invalid orientation: \" + orientation);\n    }\n    this.orientation = (byte) orientation;\n  }\n\n  @Override\n  public boolean markSupported() {\n    return false;\n  }\n\n  // No need for synchronized since all we do is throw.\n  @SuppressWarnings(\"UnsynchronizedOverridesSynchronized\")\n  @Override\n  public void mark(int readLimit) {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public int read() throws IOException {\n    final int result;\n    if (position < SEGMENT_START_POSITION || position > ORIENTATION_POSITION) {\n      result = super.read();\n    } else if (position == ORIENTATION_POSITION) {\n      result = orientation;\n    } else {\n      result = EXIF_SEGMENT[position - SEGMENT_START_POSITION] & 0xFF;\n    }\n    if (result != -1) {\n      position++;\n    }\n    return result;\n  }\n\n  @Override\n  public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {\n    int read;\n    if (position > ORIENTATION_POSITION) {\n      read = super.read(buffer, byteOffset, byteCount);\n    } else if (position == ORIENTATION_POSITION) {\n      buffer[byteOffset] = orientation;\n      read = 1;\n    } else if (position < SEGMENT_START_POSITION) {\n      read = super.read(buffer, byteOffset, SEGMENT_START_POSITION - position);\n    } else {\n      read = Math.min(ORIENTATION_POSITION - position, byteCount);\n      System.arraycopy(EXIF_SEGMENT, position - SEGMENT_START_POSITION, buffer, byteOffset, read);\n    }\n    if (read > 0) {\n      position += read;\n    }\n    return read;\n  }\n\n  @Override\n  public long skip(long byteCount) throws IOException {\n    long skipped = super.skip(byteCount);\n    if (skipped > 0) {\n      // See https://errorprone.info/bugpattern/NarrowingCompoundAssignment.\n      position = (int) (position + skipped);\n    }\n    return skipped;\n  }\n\n  // No need for synchronized since all we do is throw.\n  @SuppressWarnings(\"UnsynchronizedOverridesSynchronized\")\n  @Override\n  public void reset() throws IOException {\n    throw new UnsupportedOperationException();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/FileDescriptorAssetPathFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.AssetManager;\nimport androidx.annotation.NonNull;\nimport java.io.IOException;\n\n/** Fetches an {@link android.content.res.AssetFileDescriptor} for an asset path. */\npublic class FileDescriptorAssetPathFetcher extends AssetPathFetcher<AssetFileDescriptor> {\n  public FileDescriptorAssetPathFetcher(AssetManager assetManager, String assetPath) {\n    super(assetManager, assetPath);\n  }\n\n  @Override\n  protected AssetFileDescriptor loadResource(AssetManager assetManager, String path)\n      throws IOException {\n    return assetManager.openFd(path);\n  }\n\n  @Override\n  protected void close(AssetFileDescriptor data) throws IOException {\n    data.close();\n  }\n\n  @NonNull\n  @Override\n  public Class<AssetFileDescriptor> getDataClass() {\n    return AssetFileDescriptor.class;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/FileDescriptorLocalUriFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport android.content.ContentResolver;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport androidx.annotation.NonNull;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\n\n/** Fetches an {@link android.os.ParcelFileDescriptor} for a local {@link android.net.Uri}. */\npublic class FileDescriptorLocalUriFetcher extends LocalUriFetcher<ParcelFileDescriptor> {\n  public FileDescriptorLocalUriFetcher(ContentResolver contentResolver, Uri uri) {\n    super(contentResolver, uri);\n  }\n\n  /**\n   * useMediaStoreApisIfAvailable is part of an experiment and the constructor can be removed in a\n   * future version.\n   */\n  public FileDescriptorLocalUriFetcher(\n      ContentResolver contentResolver, Uri uri, boolean useMediaStoreApisIfAvailable) {\n    super(contentResolver, uri, useMediaStoreApisIfAvailable);\n  }\n\n  @Override\n  protected ParcelFileDescriptor loadResource(Uri uri, ContentResolver contentResolver)\n      throws FileNotFoundException {\n    AssetFileDescriptor assetFileDescriptor = openAssetFileDescriptor(uri);\n    if (assetFileDescriptor == null) {\n      throw new FileNotFoundException(\"FileDescriptor is null for: \" + uri);\n    }\n    return assetFileDescriptor.getParcelFileDescriptor();\n  }\n\n  @Override\n  protected void close(ParcelFileDescriptor data) throws IOException {\n    data.close();\n  }\n\n  @NonNull\n  @Override\n  public Class<ParcelFileDescriptor> getDataClass() {\n    return ParcelFileDescriptor.class;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/HttpUrlFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport android.text.TextUtils;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.HttpException;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.util.ContentLengthInputStream;\nimport com.bumptech.glide.util.LogTime;\nimport com.bumptech.glide.util.Synthetic;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.util.Map;\n\n/** A DataFetcher that retrieves an {@link java.io.InputStream} for a Url. */\npublic class HttpUrlFetcher implements DataFetcher<InputStream> {\n  private static final String TAG = \"HttpUrlFetcher\";\n  private static final int MAXIMUM_REDIRECTS = 5;\n  @VisibleForTesting static final String REDIRECT_HEADER_FIELD = \"Location\";\n\n  @VisibleForTesting\n  static final HttpUrlConnectionFactory DEFAULT_CONNECTION_FACTORY =\n      new DefaultHttpUrlConnectionFactory();\n\n  /** Returned when a connection error prevented us from receiving an http error. */\n  @VisibleForTesting static final int INVALID_STATUS_CODE = -1;\n\n  private final GlideUrl glideUrl;\n  private final int timeout;\n  private final HttpUrlConnectionFactory connectionFactory;\n\n  private HttpURLConnection urlConnection;\n  private InputStream stream;\n  private volatile boolean isCancelled;\n\n  public HttpUrlFetcher(GlideUrl glideUrl, int timeout) {\n    this(glideUrl, timeout, DEFAULT_CONNECTION_FACTORY);\n  }\n\n  @VisibleForTesting\n  HttpUrlFetcher(GlideUrl glideUrl, int timeout, HttpUrlConnectionFactory connectionFactory) {\n    this.glideUrl = glideUrl;\n    this.timeout = timeout;\n    this.connectionFactory = connectionFactory;\n  }\n\n  @Override\n  public void loadData(\n      @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {\n    long startTime = LogTime.getLogTime();\n    try {\n      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());\n      callback.onDataReady(result);\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to load data for url\", e);\n      }\n      callback.onLoadFailed(e);\n    } finally {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Finished http url fetcher fetch in \" + LogTime.getElapsedMillis(startTime));\n      }\n    }\n  }\n\n  private InputStream loadDataWithRedirects(\n      URL url, int redirects, URL lastUrl, Map<String, String> headers) throws HttpException {\n    if (redirects >= MAXIMUM_REDIRECTS) {\n      throw new HttpException(\n          \"Too many (> \" + MAXIMUM_REDIRECTS + \") redirects!\", INVALID_STATUS_CODE);\n    } else {\n      // Comparing the URLs using .equals performs additional network I/O and is generally broken.\n      // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.\n      try {\n        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {\n          throw new HttpException(\"In re-direct loop\", INVALID_STATUS_CODE);\n        }\n      } catch (URISyntaxException e) {\n        // Do nothing, this is best effort.\n      }\n    }\n\n    urlConnection = buildAndConfigureConnection(url, headers);\n\n    try {\n      // Connect explicitly to avoid errors in decoders if connection fails.\n      urlConnection.connect();\n      // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.\n      stream = urlConnection.getInputStream();\n    } catch (IOException e) {\n      throw new HttpException(\n          \"Failed to connect or obtain data\", getHttpStatusCodeOrInvalid(urlConnection), e);\n    }\n\n    if (isCancelled) {\n      return null;\n    }\n\n    final int statusCode = getHttpStatusCodeOrInvalid(urlConnection);\n    if (isHttpOk(statusCode)) {\n      return getStreamForSuccessfulRequest(urlConnection);\n    } else if (isHttpRedirect(statusCode)) {\n      String redirectUrlString = urlConnection.getHeaderField(REDIRECT_HEADER_FIELD);\n      if (TextUtils.isEmpty(redirectUrlString)) {\n        throw new HttpException(\"Received empty or null redirect url\", statusCode);\n      }\n      URL redirectUrl;\n      try {\n        redirectUrl = new URL(url, redirectUrlString);\n      } catch (MalformedURLException e) {\n        throw new HttpException(\"Bad redirect url: \" + redirectUrlString, statusCode, e);\n      }\n      // Closing the stream specifically is required to avoid leaking ResponseBodys in addition\n      // to disconnecting the url connection below. See #2352.\n      cleanup();\n      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);\n    } else if (statusCode == INVALID_STATUS_CODE) {\n      throw new HttpException(statusCode);\n    } else {\n      try {\n        throw new HttpException(urlConnection.getResponseMessage(), statusCode);\n      } catch (IOException e) {\n        throw new HttpException(\"Failed to get a response message\", statusCode, e);\n      }\n    }\n  }\n\n  private static int getHttpStatusCodeOrInvalid(HttpURLConnection urlConnection) {\n    try {\n      return urlConnection.getResponseCode();\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to get a response code\", e);\n      }\n    }\n    return INVALID_STATUS_CODE;\n  }\n\n  private HttpURLConnection buildAndConfigureConnection(URL url, Map<String, String> headers)\n      throws HttpException {\n    HttpURLConnection urlConnection;\n    try {\n      urlConnection = connectionFactory.build(url);\n    } catch (IOException e) {\n      throw new HttpException(\"URL.openConnection threw\", /* statusCode= */ 0, e);\n    }\n    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {\n      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());\n    }\n    urlConnection.setConnectTimeout(timeout);\n    urlConnection.setReadTimeout(timeout);\n    urlConnection.setUseCaches(false);\n    urlConnection.setDoInput(true);\n    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that\n    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.\n    urlConnection.setInstanceFollowRedirects(false);\n    return urlConnection;\n  }\n\n  // Referencing constants is less clear than a simple static method.\n  private static boolean isHttpOk(int statusCode) {\n    return statusCode / 100 == 2;\n  }\n\n  // Referencing constants is less clear than a simple static method.\n  private static boolean isHttpRedirect(int statusCode) {\n    return statusCode / 100 == 3;\n  }\n\n  private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)\n      throws HttpException {\n    try {\n      if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {\n        int contentLength = urlConnection.getContentLength();\n        stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);\n      } else {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Got non empty content encoding: \" + urlConnection.getContentEncoding());\n        }\n        stream = urlConnection.getInputStream();\n      }\n    } catch (IOException e) {\n      throw new HttpException(\n          \"Failed to obtain InputStream\", getHttpStatusCodeOrInvalid(urlConnection), e);\n    }\n    return stream;\n  }\n\n  @Override\n  public void cleanup() {\n    if (stream != null) {\n      try {\n        stream.close();\n      } catch (IOException e) {\n        // Ignore\n      }\n    }\n    if (urlConnection != null) {\n      urlConnection.disconnect();\n    }\n    urlConnection = null;\n  }\n\n  @Override\n  public void cancel() {\n    // TODO: we should consider disconnecting the url connection here, but we can't do so\n    // directly because cancel is often called on the main thread.\n    isCancelled = true;\n  }\n\n  @NonNull\n  @Override\n  public Class<InputStream> getDataClass() {\n    return InputStream.class;\n  }\n\n  @NonNull\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.REMOTE;\n  }\n\n  interface HttpUrlConnectionFactory {\n    HttpURLConnection build(URL url) throws IOException;\n  }\n\n  private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {\n\n    @Synthetic\n    DefaultHttpUrlConnectionFactory() {}\n\n    @Override\n    public HttpURLConnection build(URL url) throws IOException {\n      return (HttpURLConnection) url.openConnection();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/InputStreamRewinder.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;\nimport com.bumptech.glide.util.Synthetic;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Implementation for {@link InputStream}s that rewinds streams by wrapping them in a buffered\n * stream.\n */\npublic final class InputStreamRewinder implements DataRewinder<InputStream> {\n  // 5MB.\n  private static final int MARK_READ_LIMIT = 5 * 1024 * 1024;\n\n  private final RecyclableBufferedInputStream bufferedStream;\n\n  @Synthetic\n  public InputStreamRewinder(InputStream is, ArrayPool byteArrayPool) {\n    // We don't check is.markSupported() here because RecyclableBufferedInputStream allows resetting\n    // after exceeding MARK_READ_LIMIT, which other InputStreams don't guarantee.\n    bufferedStream = new RecyclableBufferedInputStream(is, byteArrayPool);\n    bufferedStream.mark(MARK_READ_LIMIT);\n  }\n\n  @NonNull\n  @Override\n  public InputStream rewindAndGet() throws IOException {\n    bufferedStream.reset();\n    return bufferedStream;\n  }\n\n  @Override\n  public void cleanup() {\n    bufferedStream.release();\n  }\n\n  public void fixMarkLimits() {\n    bufferedStream.fixMarkLimit();\n  }\n\n  /**\n   * Factory for producing {@link com.bumptech.glide.load.data.InputStreamRewinder}s from {@link\n   * java.io.InputStream}s.\n   */\n  public static final class Factory implements DataRewinder.Factory<InputStream> {\n    private final ArrayPool byteArrayPool;\n\n    public Factory(ArrayPool byteArrayPool) {\n      this.byteArrayPool = byteArrayPool;\n    }\n\n    @NonNull\n    @Override\n    public DataRewinder<InputStream> build(InputStream data) {\n      return new InputStreamRewinder(data, byteArrayPool);\n    }\n\n    @NonNull\n    @Override\n    public Class<InputStream> getDataClass() {\n      return InputStream.class;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/LocalUriFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport android.content.ContentResolver;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.data.mediastore.MediaStoreUtil;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\n\n/**\n * A DataFetcher that uses an {@link android.content.ContentResolver} to load data from a {@link\n * android.net.Uri} pointing to a local resource.\n *\n * @param <T> The type of data that will obtained for the given uri (For example, {@link\n *     java.io.InputStream} or {@link android.os.ParcelFileDescriptor}.\n */\npublic abstract class LocalUriFetcher<T> implements DataFetcher<T> {\n  protected final boolean useMediaStoreApisIfAvailable;\n  private static final String TAG = \"LocalUriFetcher\";\n  private final Uri uri;\n  private final ContentResolver contentResolver;\n  private T data;\n\n  /**\n   * Opens an input stream for a uri pointing to a local asset. Only certain uris are supported\n   *\n   * @param contentResolver Any {@link android.content.ContentResolver}.\n   * @param uri A Uri pointing to a local asset. This load will fail if the uri isn't openable by\n   *     {@link ContentResolver#openInputStream(android.net.Uri)}\n   * @see ContentResolver#openInputStream(android.net.Uri)\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public LocalUriFetcher(ContentResolver contentResolver, Uri uri) {\n    this(contentResolver, uri, /* useMediaStoreApisIfAvailable */ false);\n  }\n\n  /**\n   * Opens an input stream for a uri pointing to a local asset. Only certain uris are supported\n   *\n   * @param contentResolver Any {@link android.content.ContentResolver}.\n   * @param uri A Uri pointing to a local asset. This load will fail if the uri isn't openable by\n   *     {@link ContentResolver#openInputStream(android.net.Uri)}\n   * @param useMediaStoreApisIfAvailable used to decide if the uri should be opened using MediaStore\n   *     APIs\n   * @see ContentResolver#openInputStream(android.net.Uri)\n   */\n  LocalUriFetcher(ContentResolver contentResolver, Uri uri, boolean useMediaStoreApisIfAvailable) {\n    this.contentResolver = contentResolver;\n    this.uri = uri;\n    this.useMediaStoreApisIfAvailable = useMediaStoreApisIfAvailable;\n  }\n\n  @Override\n  public final void loadData(\n      @NonNull Priority priority, @NonNull DataCallback<? super T> callback) {\n    try {\n      data = loadResource(uri, contentResolver);\n      callback.onDataReady(data);\n    } catch (FileNotFoundException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to open Uri\", e);\n      }\n      callback.onLoadFailed(e);\n    }\n  }\n\n  @Override\n  public void cleanup() {\n    if (data != null) {\n      try {\n        close(data);\n      } catch (IOException e) {\n        // Ignored.\n      }\n    }\n  }\n\n  @Override\n  public void cancel() {\n    // Do nothing.\n  }\n\n  @NonNull\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.LOCAL;\n  }\n\n  /**\n   * Opens an {@link AssetFileDescriptor} for a uri pointing to a local asset. Depending on the\n   * {@code useMediaStoreApisIfAvailable} flag and the availability of MediaStore APIs, the uri may\n   * be opened using MediaStore APIs or {@link\n   * ContentResolver#openAssetFileDescriptor(android.net.Uri, String)}.\n   *\n   * @param uri A Uri pointing to a local asset.\n   */\n  protected AssetFileDescriptor openAssetFileDescriptor(Uri uri) throws FileNotFoundException {\n    return useMediaStoreApisIfAvailable\n            && MediaStoreUtil.isMediaStoreUri(uri)\n            && MediaStoreUtil.isMediaStoreOpenFileApisAvailable()\n        ? MediaStoreUtil.openAssetFileDescriptor(uri, contentResolver)\n        : contentResolver.openAssetFileDescriptor(uri, \"r\");\n  }\n\n  /**\n   * Returns a concrete data type from the given {@link android.net.Uri} using the given {@link\n   * android.content.ContentResolver}.\n   */\n  protected abstract T loadResource(Uri uri, ContentResolver contentResolver)\n      throws FileNotFoundException;\n\n  /**\n   * Closes the concrete data type if necessary.\n   *\n   * <p>Note - We can't rely on the closeable interface because it was added after our min API\n   * level. See issue #157.\n   *\n   * @param data The data to close.\n   */\n  protected abstract void close(T data) throws IOException;\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/ParcelFileDescriptorRewinder.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static android.system.OsConstants.SEEK_SET;\n\nimport android.os.Build;\nimport android.os.ParcelFileDescriptor;\nimport android.system.ErrnoException;\nimport android.system.Os;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport java.io.IOException;\n\n/**\n * Implementation for {@link ParcelFileDescriptor}s that rewinds file descriptors by seeking to 0.\n */\npublic final class ParcelFileDescriptorRewinder implements DataRewinder<ParcelFileDescriptor> {\n\n  private final InternalRewinder rewinder;\n\n  public static boolean isSupported() {\n    // Os.lseek() is only supported on API 21+ and does not work in Robolectric.\n    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP\n        && !\"robolectric\".equals(Build.FINGERPRINT);\n  }\n\n  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n  public ParcelFileDescriptorRewinder(ParcelFileDescriptor parcelFileDescriptor) {\n    rewinder = new InternalRewinder(parcelFileDescriptor);\n  }\n\n  @NonNull\n  @Override\n  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n  public ParcelFileDescriptor rewindAndGet() throws IOException {\n    return rewinder.rewind();\n  }\n\n  @Override\n  public void cleanup() {\n    // Do nothing.\n  }\n\n  /**\n   * Factory for producing {@link ParcelFileDescriptorRewinder}s from {@link ParcelFileDescriptor}s.\n   */\n  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n  public static final class Factory implements DataRewinder.Factory<ParcelFileDescriptor> {\n\n    @NonNull\n    @Override\n    public DataRewinder<ParcelFileDescriptor> build(\n        @NonNull ParcelFileDescriptor parcelFileDescriptor) {\n      return new ParcelFileDescriptorRewinder(parcelFileDescriptor);\n    }\n\n    @NonNull\n    @Override\n    public Class<ParcelFileDescriptor> getDataClass() {\n      return ParcelFileDescriptor.class;\n    }\n  }\n\n  /**\n   * Catching ErrnoException cannot be done in classes that are loaded on APIs < Lollipop. To make\n   * sure that we do not do so, we catch inside this inner class instead of the outer class. The\n   * only reason this class exists is to avoid VerifyError on older APIs.\n   */\n  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n  private static final class InternalRewinder {\n    private final ParcelFileDescriptor parcelFileDescriptor;\n\n    InternalRewinder(ParcelFileDescriptor parcelFileDescriptor) {\n      this.parcelFileDescriptor = parcelFileDescriptor;\n    }\n\n    ParcelFileDescriptor rewind() throws IOException {\n      try {\n        Os.lseek(parcelFileDescriptor.getFileDescriptor(), 0, SEEK_SET);\n      } catch (ErrnoException e) {\n        throw new IOException(e);\n      }\n      return parcelFileDescriptor;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/StreamAssetPathFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport android.content.res.AssetManager;\nimport androidx.annotation.NonNull;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/** Fetches an {@link java.io.InputStream} for an asset path. */\npublic class StreamAssetPathFetcher extends AssetPathFetcher<InputStream> {\n  public StreamAssetPathFetcher(AssetManager assetManager, String assetPath) {\n    super(assetManager, assetPath);\n  }\n\n  @Override\n  protected InputStream loadResource(AssetManager assetManager, String path) throws IOException {\n    return assetManager.open(path);\n  }\n\n  @Override\n  protected void close(InputStream data) throws IOException {\n    data.close();\n  }\n\n  @NonNull\n  @Override\n  public Class<InputStream> getDataClass() {\n    return InputStream.class;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/StreamLocalUriFetcher.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport android.content.ContentResolver;\nimport android.content.UriMatcher;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.os.Build.VERSION_CODES;\nimport android.provider.ContactsContract;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresExtension;\nimport com.bumptech.glide.load.data.mediastore.MediaStoreUtil;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/** Fetches an {@link java.io.InputStream} for a local {@link android.net.Uri}. */\npublic class StreamLocalUriFetcher extends LocalUriFetcher<InputStream> {\n  /** A lookup uri (e.g. content://com.android.contacts/contacts/lookup/3570i61d948d30808e537) */\n  private static final int ID_CONTACTS_LOOKUP = 1;\n\n  /** A contact thumbnail uri (e.g. content://com.android.contacts/contacts/38/photo) */\n  private static final int ID_CONTACTS_THUMBNAIL = 2;\n\n  /** A contact uri (e.g. content://com.android.contacts/contacts/38) */\n  private static final int ID_CONTACTS_CONTACT = 3;\n\n  /**\n   * A contact display photo (high resolution) uri (e.g.\n   * content://com.android.contacts/5/display_photo)\n   */\n  private static final int ID_CONTACTS_PHOTO = 4;\n\n  /**\n   * Uri for optimized search of phones by number (e.g.\n   * content://com.android.contacts/phone_lookup/232323232\n   */\n  private static final int ID_LOOKUP_BY_PHONE = 5;\n\n  /** Match the incoming Uri for special cases which we can handle nicely. */\n  private static final UriMatcher URI_MATCHER;\n\n  static {\n    URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);\n    URI_MATCHER.addURI(ContactsContract.AUTHORITY, \"contacts/lookup/*/#\", ID_CONTACTS_LOOKUP);\n    URI_MATCHER.addURI(ContactsContract.AUTHORITY, \"contacts/lookup/*\", ID_CONTACTS_LOOKUP);\n    URI_MATCHER.addURI(ContactsContract.AUTHORITY, \"contacts/#/photo\", ID_CONTACTS_THUMBNAIL);\n    URI_MATCHER.addURI(ContactsContract.AUTHORITY, \"contacts/#\", ID_CONTACTS_CONTACT);\n    URI_MATCHER.addURI(ContactsContract.AUTHORITY, \"contacts/#/display_photo\", ID_CONTACTS_PHOTO);\n    URI_MATCHER.addURI(ContactsContract.AUTHORITY, \"phone_lookup/*\", ID_LOOKUP_BY_PHONE);\n  }\n\n  public StreamLocalUriFetcher(ContentResolver resolver, Uri uri) {\n    super(resolver, uri);\n  }\n\n  /**\n   * useMediaStoreApisIfAvailable is part of an experiment and the constructor can be removed in a\n   * future version.\n   */\n  public StreamLocalUriFetcher(\n      ContentResolver resolver, Uri uri, boolean useMediaStoreApisIfAvailable) {\n    super(resolver, uri, useMediaStoreApisIfAvailable);\n  }\n\n  @Override\n  protected InputStream loadResource(Uri uri, ContentResolver contentResolver)\n      throws FileNotFoundException {\n    InputStream inputStream = loadResourceFromUri(uri, contentResolver);\n    if (inputStream == null) {\n      throw new FileNotFoundException(\"InputStream is null for \" + uri);\n    }\n    return inputStream;\n  }\n\n  private InputStream loadResourceFromUri(Uri uri, ContentResolver contentResolver)\n      throws FileNotFoundException {\n    switch (URI_MATCHER.match(uri)) {\n      case ID_CONTACTS_CONTACT:\n        return openContactPhotoInputStream(contentResolver, uri);\n      case ID_CONTACTS_LOOKUP:\n      case ID_LOOKUP_BY_PHONE:\n        // If it was a Lookup uri then resolve it first, then continue loading the contact uri.\n        uri = ContactsContract.Contacts.lookupContact(contentResolver, uri);\n        if (uri == null) {\n          throw new FileNotFoundException(\"Contact cannot be found\");\n        }\n        return openContactPhotoInputStream(contentResolver, uri);\n      case ID_CONTACTS_THUMBNAIL:\n      case ID_CONTACTS_PHOTO:\n      case UriMatcher.NO_MATCH:\n      default:\n        if (useMediaStoreApisIfAvailable\n            && MediaStoreUtil.isMediaStoreUri(uri)\n            && MediaStoreUtil.isMediaStoreOpenFileApisAvailable()) {\n          return openMediaStoreFileInputStream(uri, contentResolver);\n        } else {\n          return contentResolver.openInputStream(uri);\n        }\n    }\n  }\n\n  private InputStream openContactPhotoInputStream(ContentResolver contentResolver, Uri contactUri) {\n    return ContactsContract.Contacts.openContactPhotoInputStream(\n        contentResolver, contactUri, true /*preferHighres*/);\n  }\n\n  @RequiresExtension(\n      extension = VERSION_CODES.R,\n      version = MediaStoreUtil.MIN_EXTENSION_VERSION_FOR_OPEN_FILE_APIS)\n  private InputStream openMediaStoreFileInputStream(Uri uri, ContentResolver contentResolver)\n      throws FileNotFoundException {\n    AssetFileDescriptor assetFileDescriptor =\n        MediaStoreUtil.openAssetFileDescriptor(uri, contentResolver);\n    if (assetFileDescriptor == null) {\n      throw new FileNotFoundException(\"FileDescriptor is null for: \" + uri);\n    }\n    try {\n      return assetFileDescriptor.createInputStream();\n    } catch (IOException exception) {\n      try {\n        assetFileDescriptor.close();\n      } catch (Exception innerException) {\n        // Ignored\n      }\n      throw (FileNotFoundException)\n          new FileNotFoundException(\"Unable to create stream\").initCause(exception);\n    }\n  }\n\n  @Override\n  protected void close(InputStream data) throws IOException {\n    data.close();\n  }\n\n  @NonNull\n  @Override\n  public Class<InputStream> getDataClass() {\n    return InputStream.class;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/mediastore/FileService.java",
    "content": "package com.bumptech.glide.load.data.mediastore;\n\nimport java.io.File;\n\nclass FileService {\n  public boolean exists(File file) {\n    return file.exists();\n  }\n\n  public long length(File file) {\n    return file.length();\n  }\n\n  public File get(String path) {\n    return new File(path);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/mediastore/MediaStoreUtil.java",
    "content": "package com.bumptech.glide.load.data.mediastore;\n\nimport android.content.ContentResolver;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Build.VERSION_CODES;\nimport android.os.ext.SdkExtensions;\nimport android.provider.MediaStore;\nimport androidx.annotation.ChecksSdkIntAtLeast;\nimport androidx.annotation.RequiresExtension;\nimport com.bumptech.glide.request.target.Target;\nimport java.io.FileNotFoundException;\n\n/** Utility classes for interacting with the media store. */\npublic final class MediaStoreUtil {\n  public static final int MIN_EXTENSION_VERSION_FOR_OPEN_FILE_APIS = 17;\n  private static final int MINI_THUMB_WIDTH = 512;\n  private static final int MINI_THUMB_HEIGHT = 384;\n\n  private MediaStoreUtil() {\n    // Utility class.\n  }\n\n  public static boolean isMediaStoreUri(Uri uri) {\n    return uri != null\n        && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())\n        && MediaStore.AUTHORITY.equals(uri.getAuthority());\n  }\n\n  @ChecksSdkIntAtLeast(api = MIN_EXTENSION_VERSION_FOR_OPEN_FILE_APIS, extension = VERSION_CODES.R)\n  public static boolean isMediaStoreOpenFileApisAvailable() {\n    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R\n        && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R)\n            >= MIN_EXTENSION_VERSION_FOR_OPEN_FILE_APIS;\n  }\n\n  @RequiresExtension(\n      extension = VERSION_CODES.R,\n      version = MIN_EXTENSION_VERSION_FOR_OPEN_FILE_APIS)\n  public static AssetFileDescriptor openAssetFileDescriptor(\n      Uri uri, ContentResolver contentResolver) throws FileNotFoundException {\n    return MediaStore.openAssetFileDescriptor(contentResolver, uri, \"r\", null);\n  }\n\n  // Android picker uris contain a \"picker\" segment:\n  // https://android.googlesource.com/platform/packages/providers/MediaProvider/+/refs/heads/master/src/com/android/providers/media/PickerUriResolver.java#58\n  public static boolean isAndroidPickerUri(Uri uri) {\n    return isMediaStoreUri(uri) && uri.getPathSegments().contains(\"picker\");\n  }\n\n  private static boolean isVideoUri(Uri uri) {\n    return uri.getPathSegments().contains(\"video\");\n  }\n\n  public static boolean isMediaStoreVideoUri(Uri uri) {\n    return isMediaStoreUri(uri) && isVideoUri(uri);\n  }\n\n  public static boolean isMediaStoreImageUri(Uri uri) {\n    return isMediaStoreUri(uri) && !isVideoUri(uri);\n  }\n\n  public static boolean isThumbnailSize(int width, int height) {\n    return width != Target.SIZE_ORIGINAL\n        && height != Target.SIZE_ORIGINAL\n        && width <= MINI_THUMB_WIDTH\n        && height <= MINI_THUMB_HEIGHT;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/mediastore/ThumbFetcher.java",
    "content": "package com.bumptech.glide.load.data.mediastore;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.MediaStore;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.ExifOrientationStream;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * A {@link DataFetcher} implementation for {@link InputStream}s that loads data from thumbnail\n * files obtained from the {@link MediaStore}.\n */\n@SuppressWarnings(\"PMD.FieldDeclarationsShouldBeAtStartOfClass\")\npublic class ThumbFetcher implements DataFetcher<InputStream> {\n  private static final String TAG = \"MediaStoreThumbFetcher\";\n  private final Uri mediaStoreImageUri;\n  private final ThumbnailStreamOpener opener;\n  private InputStream inputStream;\n\n  public static ThumbFetcher buildImageFetcher(Context context, Uri uri) {\n    return build(context, uri, new ImageThumbnailQuery(context.getContentResolver()));\n  }\n\n  public static ThumbFetcher buildVideoFetcher(Context context, Uri uri) {\n    return build(context, uri, new VideoThumbnailQuery(context.getContentResolver()));\n  }\n\n  private static ThumbFetcher build(Context context, Uri uri, ThumbnailQuery query) {\n    ArrayPool byteArrayPool = Glide.get(context).getArrayPool();\n    ThumbnailStreamOpener opener =\n        new ThumbnailStreamOpener(\n            Glide.get(context).getRegistry().getImageHeaderParsers(),\n            query,\n            byteArrayPool,\n            context.getContentResolver());\n    return new ThumbFetcher(uri, opener);\n  }\n\n  @VisibleForTesting\n  ThumbFetcher(Uri mediaStoreImageUri, ThumbnailStreamOpener opener) {\n    this.mediaStoreImageUri = mediaStoreImageUri;\n    this.opener = opener;\n  }\n\n  @Override\n  public void loadData(\n      @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {\n    try {\n      inputStream = openThumbInputStream();\n      callback.onDataReady(inputStream);\n    } catch (FileNotFoundException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to find thumbnail file\", e);\n      }\n      callback.onLoadFailed(e);\n    }\n  }\n\n  private InputStream openThumbInputStream() throws FileNotFoundException {\n    InputStream result = opener.open(mediaStoreImageUri);\n\n    int orientation = -1;\n    if (result != null) {\n      orientation = opener.getOrientation(mediaStoreImageUri);\n    }\n\n    if (orientation != -1) {\n      result = new ExifOrientationStream(result, orientation);\n    }\n    return result;\n  }\n\n  @Override\n  public void cleanup() {\n    if (inputStream != null) {\n      try {\n        inputStream.close();\n      } catch (IOException e) {\n        // Ignored.\n      }\n    }\n  }\n\n  @Override\n  public void cancel() {\n    // Do nothing.\n  }\n\n  @NonNull\n  @Override\n  public Class<InputStream> getDataClass() {\n    return InputStream.class;\n  }\n\n  @NonNull\n  @Override\n  public DataSource getDataSource() {\n    return DataSource.LOCAL;\n  }\n\n  static class VideoThumbnailQuery implements ThumbnailQuery {\n\n    private final ContentResolver contentResolver;\n\n    VideoThumbnailQuery(ContentResolver contentResolver) {\n      this.contentResolver = contentResolver;\n    }\n\n    private static final String[] PATH_PROJECTION = {MediaStore.Video.Thumbnails.DATA};\n    private static final String PATH_SELECTION =\n        MediaStore.Video.Thumbnails.KIND\n            + \" = \"\n            + MediaStore.Video.Thumbnails.MINI_KIND\n            + \" AND \"\n            + MediaStore.Video.Thumbnails.VIDEO_ID\n            + \" = ?\";\n\n    @Override\n    public Cursor query(Uri uri) {\n      String videoId = uri.getLastPathSegment();\n      return contentResolver.query(\n          MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI,\n          PATH_PROJECTION,\n          PATH_SELECTION,\n          new String[] {videoId},\n          null /*sortOrder*/);\n    }\n  }\n\n  static class ImageThumbnailQuery implements ThumbnailQuery {\n\n    private final ContentResolver contentResolver;\n\n    ImageThumbnailQuery(ContentResolver contentResolver) {\n      this.contentResolver = contentResolver;\n    }\n\n    private static final String[] PATH_PROJECTION = {\n      MediaStore.Images.Thumbnails.DATA,\n    };\n    private static final String PATH_SELECTION =\n        MediaStore.Images.Thumbnails.KIND\n            + \" = \"\n            + MediaStore.Images.Thumbnails.MINI_KIND\n            + \" AND \"\n            + MediaStore.Images.Thumbnails.IMAGE_ID\n            + \" = ?\";\n\n    @Override\n    public Cursor query(Uri uri) {\n      String imageId = uri.getLastPathSegment();\n      return contentResolver.query(\n          MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,\n          PATH_PROJECTION,\n          PATH_SELECTION,\n          new String[] {imageId},\n          null /*sortOrder*/);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/mediastore/ThumbnailQuery.java",
    "content": "package com.bumptech.glide.load.data.mediastore;\n\nimport android.database.Cursor;\nimport android.net.Uri;\n\ninterface ThumbnailQuery {\n  Cursor query(Uri uri);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/data/mediastore/ThumbnailStreamOpener.java",
    "content": "package com.bumptech.glide.load.data.mediastore;\n\nimport android.content.ContentResolver;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParserUtils;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\n\nclass ThumbnailStreamOpener {\n  private static final String TAG = \"ThumbStreamOpener\";\n  private static final FileService DEFAULT_SERVICE = new FileService();\n\n  private final FileService service;\n  private final ThumbnailQuery query;\n  private final ArrayPool byteArrayPool;\n  private final ContentResolver contentResolver;\n  private final List<ImageHeaderParser> parsers;\n\n  ThumbnailStreamOpener(\n      List<ImageHeaderParser> parsers,\n      ThumbnailQuery query,\n      ArrayPool byteArrayPool,\n      ContentResolver contentResolver) {\n    this(parsers, DEFAULT_SERVICE, query, byteArrayPool, contentResolver);\n  }\n\n  ThumbnailStreamOpener(\n      List<ImageHeaderParser> parsers,\n      FileService service,\n      ThumbnailQuery query,\n      ArrayPool byteArrayPool,\n      ContentResolver contentResolver) {\n    this.service = service;\n    this.query = query;\n    this.byteArrayPool = byteArrayPool;\n    this.contentResolver = contentResolver;\n    this.parsers = parsers;\n  }\n\n  int getOrientation(Uri uri) {\n    InputStream is = null;\n    try {\n      is = contentResolver.openInputStream(uri);\n      return ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);\n      // PMD.AvoidCatchingNPE framework method openInputStream can throw NPEs.\n    } catch (@SuppressWarnings(\"PMD.AvoidCatchingNPE\") IOException | NullPointerException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to open uri: \" + uri, e);\n      }\n    } finally {\n      if (is != null) {\n        try {\n          is.close();\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n    }\n    return ImageHeaderParser.UNKNOWN_ORIENTATION;\n  }\n\n  public InputStream open(Uri uri) throws FileNotFoundException {\n    String path = getPath(uri);\n    if (TextUtils.isEmpty(path)) {\n      return null;\n    }\n\n    File file = service.get(path);\n    if (!isValid(file)) {\n      return null;\n    }\n\n    Uri thumbnailUri = Uri.fromFile(file);\n    try {\n      return contentResolver.openInputStream(thumbnailUri);\n      // PMD.AvoidCatchingNPE framework method openInputStream can throw NPEs.\n    } catch (\n        @SuppressWarnings(\"PMD.AvoidCatchingNPE\")\n        NullPointerException e) {\n      throw (FileNotFoundException)\n          new FileNotFoundException(\"NPE opening uri: \" + uri + \" -> \" + thumbnailUri).initCause(e);\n    }\n  }\n\n  @Nullable\n  private String getPath(@NonNull Uri uri) {\n    Cursor cursor = null;\n    try {\n      cursor = query.query(uri);\n      if (cursor != null && cursor.moveToFirst()) {\n        return cursor.getString(0);\n      } else {\n        return null;\n      }\n    } catch (SecurityException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to query for thumbnail for Uri: \" + uri, e);\n      }\n      return null;\n    } finally {\n      if (cursor != null) {\n        cursor.close();\n      }\n    }\n  }\n\n  private boolean isValid(File file) {\n    return service.exists(file) && 0 < service.length(file);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/ActiveResources.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport android.os.Process;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.EngineResource.ResourceListener;\nimport com.bumptech.glide.util.Executors;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport java.lang.ref.ReferenceQueue;\nimport java.lang.ref.WeakReference;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.ThreadFactory;\n\nfinal class ActiveResources {\n  private final boolean isActiveResourceRetentionAllowed;\n  private final Executor monitorClearedResourcesExecutor;\n  @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();\n  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();\n\n  private ResourceListener listener;\n\n  private volatile boolean isShutdown;\n  @Nullable private volatile DequeuedResourceCallback cb;\n\n  ActiveResources(boolean isActiveResourceRetentionAllowed) {\n    this(\n        isActiveResourceRetentionAllowed,\n        java.util.concurrent.Executors.newSingleThreadExecutor(\n            new ThreadFactory() {\n              @Override\n              public Thread newThread(@NonNull final Runnable r) {\n                return new Thread(\n                    new Runnable() {\n                      @Override\n                      public void run() {\n                        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);\n                        r.run();\n                      }\n                    },\n                    \"glide-active-resources\");\n              }\n            }));\n  }\n\n  @VisibleForTesting\n  ActiveResources(\n      boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {\n    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;\n    this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;\n\n    monitorClearedResourcesExecutor.execute(\n        new Runnable() {\n          @Override\n          public void run() {\n            cleanReferenceQueue();\n          }\n        });\n  }\n\n  void setListener(ResourceListener listener) {\n    synchronized (listener) {\n      synchronized (this) {\n        this.listener = listener;\n      }\n    }\n  }\n\n  synchronized void activate(Key key, EngineResource<?> resource) {\n    ResourceWeakReference toPut =\n        new ResourceWeakReference(\n            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);\n\n    ResourceWeakReference removed = activeEngineResources.put(key, toPut);\n    if (removed != null) {\n      removed.reset();\n    }\n  }\n\n  synchronized void deactivate(Key key) {\n    ResourceWeakReference removed = activeEngineResources.remove(key);\n    if (removed != null) {\n      removed.reset();\n    }\n  }\n\n  @Nullable\n  synchronized EngineResource<?> get(Key key) {\n    ResourceWeakReference activeRef = activeEngineResources.get(key);\n    if (activeRef == null) {\n      return null;\n    }\n\n    EngineResource<?> active = activeRef.get();\n    if (active == null) {\n      cleanupActiveReference(activeRef);\n    }\n    return active;\n  }\n\n  @SuppressWarnings({\"WeakerAccess\", \"SynchronizeOnNonFinalField\"})\n  @Synthetic\n  void cleanupActiveReference(@NonNull ResourceWeakReference ref) {\n    synchronized (this) {\n      activeEngineResources.remove(ref.key);\n\n      if (!ref.isCacheable || ref.resource == null) {\n        return;\n      }\n    }\n\n    EngineResource<?> newResource =\n        new EngineResource<>(\n            ref.resource,\n            /* isMemoryCacheable= */ true,\n            /* isRecyclable= */ false,\n            ref.key,\n            listener);\n    listener.onResourceReleased(ref.key, newResource);\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  void cleanReferenceQueue() {\n    while (!isShutdown) {\n      try {\n        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();\n        cleanupActiveReference(ref);\n\n        // This section for testing only.\n        DequeuedResourceCallback current = cb;\n        if (current != null) {\n          current.onResourceDequeued();\n        }\n        // End for testing only.\n      } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n      }\n    }\n  }\n\n  @VisibleForTesting\n  void setDequeuedResourceCallback(DequeuedResourceCallback cb) {\n    this.cb = cb;\n  }\n\n  @VisibleForTesting\n  interface DequeuedResourceCallback {\n    void onResourceDequeued();\n  }\n\n  @VisibleForTesting\n  void shutdown() {\n    isShutdown = true;\n    if (monitorClearedResourcesExecutor instanceof ExecutorService) {\n      ExecutorService service = (ExecutorService) monitorClearedResourcesExecutor;\n      Executors.shutdownAndAwaitTermination(service);\n    }\n  }\n\n  @VisibleForTesting\n  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {\n    @SuppressWarnings(\"WeakerAccess\")\n    @Synthetic\n    final Key key;\n\n    @SuppressWarnings(\"WeakerAccess\")\n    @Synthetic\n    final boolean isCacheable;\n\n    @Nullable\n    @SuppressWarnings(\"WeakerAccess\")\n    @Synthetic\n    Resource<?> resource;\n\n    @Synthetic\n    @SuppressWarnings(\"WeakerAccess\")\n    ResourceWeakReference(\n        @NonNull Key key,\n        @NonNull EngineResource<?> referent,\n        @NonNull ReferenceQueue<? super EngineResource<?>> queue,\n        boolean isActiveResourceRetentionAllowed) {\n      super(referent, queue);\n      this.key = Preconditions.checkNotNull(key);\n      this.resource =\n          referent.isMemoryCacheable() && isActiveResourceRetentionAllowed\n              ? Preconditions.checkNotNull(referent.getResource())\n              : null;\n      isCacheable = referent.isMemoryCacheable();\n    }\n\n    void reset() {\n      resource = null;\n      clear();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/CallbackException.java",
    "content": "package com.bumptech.glide.load.engine;\n\n/**\n * An exception indicating that code outside of Glide threw an unexpected exception.\n *\n * <p>This is useful to allow us to distinguish developer errors on the part of users of Glide from\n * developer errors on the part of developers of Glide itself.\n */\nfinal class CallbackException extends RuntimeException {\n  private static final long serialVersionUID = -7530898992688511851L;\n\n  CallbackException(Throwable cause) {\n    super(\"Unexpected exception thrown by non-Glide code\", cause);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/DataCacheGenerator.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoader.LoadData;\nimport com.bumptech.glide.util.pool.GlideTrace;\nimport java.io.File;\nimport java.util.List;\n\n/**\n * Generates {@link com.bumptech.glide.load.data.DataFetcher DataFetchers} from cache files\n * containing original unmodified source data.\n */\nclass DataCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {\n\n  private final List<Key> cacheKeys;\n  private final DecodeHelper<?> helper;\n  private final FetcherReadyCallback cb;\n\n  private int sourceIdIndex = -1;\n  private Key sourceKey;\n  private List<ModelLoader<File, ?>> modelLoaders;\n  private int modelLoaderIndex;\n  private volatile LoadData<?> loadData;\n\n  // PMD is wrong here, this File must be an instance variable because it may be used across\n  // multiple calls to startNext.\n  @SuppressWarnings(\"PMD.SingularField\")\n  private File cacheFile;\n\n  DataCacheGenerator(DecodeHelper<?> helper, FetcherReadyCallback cb) {\n    this(helper.getCacheKeys(), helper, cb);\n  }\n\n  // In some cases we may want to load a specific cache key (when loading from source written to\n  // cache), so we accept a list of keys rather than just obtain the list from the helper.\n  DataCacheGenerator(List<Key> cacheKeys, DecodeHelper<?> helper, FetcherReadyCallback cb) {\n    this.cacheKeys = cacheKeys;\n    this.helper = helper;\n    this.cb = cb;\n  }\n\n  @Override\n  public boolean startNext() {\n    GlideTrace.beginSection(\"DataCacheGenerator.startNext\");\n    try {\n      while (modelLoaders == null || !hasNextModelLoader()) {\n        sourceIdIndex++;\n        if (sourceIdIndex >= cacheKeys.size()) {\n          return false;\n        }\n\n        Key sourceId = cacheKeys.get(sourceIdIndex);\n        // PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times\n        // and the actions it performs are much more expensive than a single allocation.\n        @SuppressWarnings(\"PMD.AvoidInstantiatingObjectsInLoops\")\n        Key originalKey = new DataCacheKey(sourceId, helper.getSignature());\n        cacheFile = helper.getDiskCache().get(originalKey);\n        if (cacheFile != null) {\n          this.sourceKey = sourceId;\n          modelLoaders = helper.getModelLoaders(cacheFile);\n          modelLoaderIndex = 0;\n        }\n      }\n\n      loadData = null;\n      boolean started = false;\n      while (!started && hasNextModelLoader()) {\n        ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);\n        loadData =\n            modelLoader.buildLoadData(\n                cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());\n        if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {\n          started = true;\n          loadData.fetcher.loadData(helper.getPriority(), this);\n        }\n      }\n      return started;\n    } finally {\n      GlideTrace.endSection();\n    }\n  }\n\n  private boolean hasNextModelLoader() {\n    return modelLoaderIndex < modelLoaders.size();\n  }\n\n  @Override\n  public void cancel() {\n    LoadData<?> local = loadData;\n    if (local != null) {\n      local.fetcher.cancel();\n    }\n  }\n\n  @Override\n  public void onDataReady(Object data) {\n    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);\n  }\n\n  @Override\n  public void onLoadFailed(@NonNull Exception e) {\n    cb.onDataFetcherFailed(sourceKey, e, loadData.fetcher, DataSource.DATA_DISK_CACHE);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/DataCacheKey.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport java.security.MessageDigest;\n\n/** A cache key for original source data + any requested signature. */\nfinal class DataCacheKey implements Key {\n\n  private final Key sourceKey;\n  private final Key signature;\n\n  DataCacheKey(Key sourceKey, Key signature) {\n    this.sourceKey = sourceKey;\n    this.signature = signature;\n  }\n\n  Key getSourceKey() {\n    return sourceKey;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof DataCacheKey) {\n      DataCacheKey other = (DataCacheKey) o;\n      return sourceKey.equals(other.sourceKey) && signature.equals(other.signature);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    int result = sourceKey.hashCode();\n    result = 31 * result + signature.hashCode();\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return \"DataCacheKey{\" + \"sourceKey=\" + sourceKey + \", signature=\" + signature + '}';\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    sourceKey.updateDiskCacheKey(messageDigest);\n    signature.updateDiskCacheKey(messageDigest);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/DataCacheWriter.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Encoder;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport java.io.File;\n\n/**\n * Writes original source data or downsampled/transformed resource data to cache using the provided\n * {@link com.bumptech.glide.load.Encoder} or {@link com.bumptech.glide.load.ResourceEncoder} and\n * the given data or {@link com.bumptech.glide.load.engine.Resource}.\n *\n * @param <DataType> The type of data that will be encoded (InputStream, ByteBuffer,\n *     Resource<Bitmap> etc).\n */\nclass DataCacheWriter<DataType> implements DiskCache.Writer {\n  private final Encoder<DataType> encoder;\n  private final DataType data;\n  private final Options options;\n\n  DataCacheWriter(Encoder<DataType> encoder, DataType data, Options options) {\n    this.encoder = encoder;\n    this.data = data;\n    this.options = options;\n  }\n\n  @Override\n  public boolean write(@NonNull File file) {\n    return encoder.encode(data, file, options);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/DataFetcherGenerator.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.data.DataFetcher;\n\n/**\n * Generates a series of {@link com.bumptech.glide.load.data.DataFetcher DataFetchers} using\n * registered {@link com.bumptech.glide.load.model.ModelLoader ModelLoaders} and a model.\n */\ninterface DataFetcherGenerator {\n  /**\n   * Called when the generator has finished loading data from a {@link\n   * com.bumptech.glide.load.data.DataFetcher}.\n   */\n  interface FetcherReadyCallback {\n\n    /** Requests that we call startNext() again on a Glide owned thread. */\n    void reschedule();\n\n    /**\n     * Notifies the callback that the load is complete.\n     *\n     * @param sourceKey The id of the loaded data.\n     * @param data The loaded data, or null if the load failed.\n     * @param fetcher The data fetcher we attempted to load from.\n     * @param dataSource The data source we were loading from.\n     * @param attemptedKey The key we were loading data from (may be an alternate).\n     */\n    void onDataFetcherReady(\n        Key sourceKey,\n        @Nullable Object data,\n        DataFetcher<?> fetcher,\n        DataSource dataSource,\n        Key attemptedKey);\n\n    /**\n     * Notifies the callback when the load fails.\n     *\n     * @param attemptedKey The key we were using to load (may be an alternate).\n     * @param e The exception that caused the load to fail.\n     * @param fetcher The fetcher we were loading from.\n     * @param dataSource The data source we were loading from.\n     */\n    void onDataFetcherFailed(\n        Key attemptedKey, Exception e, DataFetcher<?> fetcher, DataSource dataSource);\n  }\n\n  /**\n   * Attempts to a single new {@link com.bumptech.glide.load.data.DataFetcher} and returns true if a\n   * {@link com.bumptech.glide.load.data.DataFetcher} was started, and false otherwise.\n   */\n  boolean startNext();\n\n  /**\n   * Attempts to cancel the currently running fetcher.\n   *\n   * <p>This will be called on the main thread and should complete quickly.\n   */\n  void cancel();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/DecodeHelper.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport com.bumptech.glide.GlideContext;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.load.Encoder;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.data.DataRewinder;\nimport com.bumptech.glide.load.engine.DecodeJob.DiskCacheProvider;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoader.LoadData;\nimport com.bumptech.glide.load.resource.UnitTransformation;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nfinal class DecodeHelper<Transcode> {\n\n  private final List<LoadData<?>> loadData = new ArrayList<>();\n  private final List<Key> cacheKeys = new ArrayList<>();\n\n  private GlideContext glideContext;\n  private Object model;\n  private int width;\n  private int height;\n  private Class<?> resourceClass;\n  private DecodeJob.DiskCacheProvider diskCacheProvider;\n  private Options options;\n  private Map<Class<?>, Transformation<?>> transformations;\n  private Class<Transcode> transcodeClass;\n  private boolean isLoadDataSet;\n  private boolean isCacheKeysSet;\n  private Key signature;\n  private Priority priority;\n  private DiskCacheStrategy diskCacheStrategy;\n  private boolean isTransformationRequired;\n  private boolean isScaleOnlyOrNoTransform;\n\n  @SuppressWarnings(\"unchecked\")\n  <R> void init(\n      GlideContext glideContext,\n      Object model,\n      Key signature,\n      int width,\n      int height,\n      DiskCacheStrategy diskCacheStrategy,\n      Class<?> resourceClass,\n      Class<R> transcodeClass,\n      Priority priority,\n      Options options,\n      Map<Class<?>, Transformation<?>> transformations,\n      boolean isTransformationRequired,\n      boolean isScaleOnlyOrNoTransform,\n      DiskCacheProvider diskCacheProvider) {\n    this.glideContext = glideContext;\n    this.model = model;\n    this.signature = signature;\n    this.width = width;\n    this.height = height;\n    this.diskCacheStrategy = diskCacheStrategy;\n    this.resourceClass = resourceClass;\n    this.diskCacheProvider = diskCacheProvider;\n    this.transcodeClass = (Class<Transcode>) transcodeClass;\n    this.priority = priority;\n    this.options = options;\n    this.transformations = transformations;\n    this.isTransformationRequired = isTransformationRequired;\n    this.isScaleOnlyOrNoTransform = isScaleOnlyOrNoTransform;\n  }\n\n  void clear() {\n    glideContext = null;\n    model = null;\n    signature = null;\n    resourceClass = null;\n    transcodeClass = null;\n    options = null;\n    priority = null;\n    transformations = null;\n    diskCacheStrategy = null;\n\n    loadData.clear();\n    isLoadDataSet = false;\n    cacheKeys.clear();\n    isCacheKeysSet = false;\n  }\n\n  DiskCache getDiskCache() {\n    return diskCacheProvider.getDiskCache();\n  }\n\n  DiskCacheStrategy getDiskCacheStrategy() {\n    return diskCacheStrategy;\n  }\n\n  <T> DataRewinder<T> getRewinder(T data) {\n    return glideContext.getRegistry().getRewinder(data);\n  }\n\n  Priority getPriority() {\n    return priority;\n  }\n\n  Options getOptions() {\n    return options;\n  }\n\n  Key getSignature() {\n    return signature;\n  }\n\n  int getWidth() {\n    return width;\n  }\n\n  int getHeight() {\n    return height;\n  }\n\n  ArrayPool getArrayPool() {\n    return glideContext.getArrayPool();\n  }\n\n  Class<?> getTranscodeClass() {\n    return transcodeClass;\n  }\n\n  Class<?> getModelClass() {\n    return model.getClass();\n  }\n\n  List<Class<?>> getRegisteredResourceClasses() {\n    return glideContext\n        .getRegistry()\n        .getRegisteredResourceClasses(model.getClass(), resourceClass, transcodeClass);\n  }\n\n  boolean hasLoadPath(Class<?> dataClass) {\n    return getLoadPath(dataClass) != null;\n  }\n\n  <Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) {\n    return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass);\n  }\n\n  boolean isScaleOnlyOrNoTransform() {\n    return isScaleOnlyOrNoTransform;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  <Z> Transformation<Z> getTransformation(Class<Z> resourceClass) {\n    Transformation<Z> result = (Transformation<Z>) transformations.get(resourceClass);\n    if (result == null) {\n      for (Entry<Class<?>, Transformation<?>> entry : transformations.entrySet()) {\n        if (entry.getKey().isAssignableFrom(resourceClass)) {\n          result = (Transformation<Z>) entry.getValue();\n          break;\n        }\n      }\n    }\n\n    if (result == null) {\n      if (transformations.isEmpty() && isTransformationRequired) {\n        throw new IllegalArgumentException(\n            \"Missing transformation for \"\n                + resourceClass\n                + \". If you wish to\"\n                + \" ignore unknown resource types, use the optional transformation methods.\");\n      } else {\n        return UnitTransformation.get();\n      }\n    }\n    return result;\n  }\n\n  boolean isResourceEncoderAvailable(Resource<?> resource) {\n    return glideContext.getRegistry().isResourceEncoderAvailable(resource);\n  }\n\n  <Z> ResourceEncoder<Z> getResultEncoder(Resource<Z> resource) {\n    return glideContext.getRegistry().getResultEncoder(resource);\n  }\n\n  List<ModelLoader<File, ?>> getModelLoaders(File file)\n      throws Registry.NoModelLoaderAvailableException {\n    return glideContext.getRegistry().getModelLoaders(file);\n  }\n\n  boolean isSourceKey(Key key) {\n    List<LoadData<?>> loadData = getLoadData();\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0, size = loadData.size(); i < size; i++) {\n      LoadData<?> current = loadData.get(i);\n      if (current.sourceKey.equals(key)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  List<LoadData<?>> getLoadData() {\n    if (!isLoadDataSet) {\n      isLoadDataSet = true;\n      loadData.clear();\n      List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);\n      //noinspection ForLoopReplaceableByForEach to improve perf\n      for (int i = 0, size = modelLoaders.size(); i < size; i++) {\n        ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);\n        LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);\n        if (current != null) {\n          loadData.add(current);\n        }\n      }\n    }\n    return loadData;\n  }\n\n  List<Key> getCacheKeys() {\n    if (!isCacheKeysSet) {\n      isCacheKeysSet = true;\n      cacheKeys.clear();\n      List<LoadData<?>> loadData = getLoadData();\n      //noinspection ForLoopReplaceableByForEach to improve perf\n      for (int i = 0, size = loadData.size(); i < size; i++) {\n        LoadData<?> data = loadData.get(i);\n        if (!cacheKeys.contains(data.sourceKey)) {\n          cacheKeys.add(data.sourceKey);\n        }\n        for (int j = 0; j < data.alternateKeys.size(); j++) {\n          if (!cacheKeys.contains(data.alternateKeys.get(j))) {\n            cacheKeys.add(data.alternateKeys.get(j));\n          }\n        }\n      }\n    }\n    return cacheKeys;\n  }\n\n  <X> Encoder<X> getSourceEncoder(X data) throws Registry.NoSourceEncoderAvailableException {\n    return glideContext.getRegistry().getSourceEncoder(data);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/DecodeJob.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport android.os.Build;\nimport android.os.Process;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.util.Pools;\nimport com.bumptech.glide.GlideBuilder.OverrideGlideThreadPriority;\nimport com.bumptech.glide.GlideContext;\nimport com.bumptech.glide.GlideExperiments;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.EncodeStrategy;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.DataRewinder;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.resource.bitmap.Downsampler;\nimport com.bumptech.glide.util.LogTime;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.pool.FactoryPools.Poolable;\nimport com.bumptech.glide.util.pool.GlideTrace;\nimport com.bumptech.glide.util.pool.StateVerifier;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\n/**\n * A class responsible for decoding resources either from cached data or from the original source\n * and applying transformations and transcodes.\n *\n * <p>Note: this class has a natural ordering that is inconsistent with equals.\n *\n * @param <R> The type of resource that will be transcoded from the decoded and transformed\n *     resource.\n */\nclass DecodeJob<R>\n    implements DataFetcherGenerator.FetcherReadyCallback,\n        Runnable,\n        Comparable<DecodeJob<?>>,\n        Poolable {\n  private static final String TAG = \"DecodeJob\";\n\n  /**\n   * {@link com.bumptech.glide.load.Option} to override the OS thread priority of the thread\n   * handling the decode job.\n   *\n   * <p>Acceptable values are integer constants defined in {@link android.os.Process}, ranging from\n   * {@link android.os.Process#THREAD_PRIORITY_LOWEST} to (-20). Any exceptions thrown will cause\n   * the override to fail silently and disable overrides on any subsequent jobs.\n   *\n   * <p>Must have {@link GlideBuilder#setOverrideGlideThreadPriority(boolean)} experiment enabled to\n   * be used.\n   *\n   * <p>This is used for a highly experimental API that may be removed in the future. Please use at\n   * your own risk.\n   */\n  public static final Option<Supplier<Integer>> GLIDE_THREAD_PRIORITY_OVERRIDE =\n      Option.memory(\"glide_thread_priority_override\");\n\n  private final DecodeHelper<R> decodeHelper = new DecodeHelper<>();\n  private final List<Throwable> throwables = new ArrayList<>();\n  private final StateVerifier stateVerifier = StateVerifier.newInstance();\n  private final DiskCacheProvider diskCacheProvider;\n  private final Pools.Pool<DecodeJob<?>> pool;\n  private final DeferredEncodeManager<?> deferredEncodeManager = new DeferredEncodeManager<>();\n  private final ReleaseManager releaseManager = new ReleaseManager();\n\n  private GlideContext glideContext;\n  private Key signature;\n  private Priority priority;\n  private EngineKey loadKey;\n  private int width;\n  private int height;\n  private DiskCacheStrategy diskCacheStrategy;\n  private Options options;\n  private Callback<R> callback;\n  private int order;\n  private Stage stage;\n  private RunReason runReason;\n  private long startFetchTime;\n  private boolean onlyRetrieveFromCache;\n  private Object model;\n  private GlideExperiments experiments;\n  @Nullable private Supplier<Integer> glideThreadPriorityOverride;\n\n  private Thread currentThread;\n  private Key currentSourceKey;\n  private Key currentAttemptingKey;\n  private Object currentData;\n  private DataSource currentDataSource;\n  private DataFetcher<?> currentFetcher;\n\n  private volatile DataFetcherGenerator currentGenerator;\n  private volatile boolean isCallbackNotified;\n  private volatile boolean isCancelled;\n  private boolean isLoadingFromAlternateCacheKey;\n\n  DecodeJob(DiskCacheProvider diskCacheProvider, Pools.Pool<DecodeJob<?>> pool) {\n    this.diskCacheProvider = diskCacheProvider;\n    this.pool = pool;\n  }\n\n  DecodeJob<R> init(\n      GlideContext glideContext,\n      Object model,\n      EngineKey loadKey,\n      Key signature,\n      int width,\n      int height,\n      Class<?> resourceClass,\n      Class<R> transcodeClass,\n      Priority priority,\n      DiskCacheStrategy diskCacheStrategy,\n      Map<Class<?>, Transformation<?>> transformations,\n      boolean isTransformationRequired,\n      boolean isScaleOnlyOrNoTransform,\n      boolean onlyRetrieveFromCache,\n      Options options,\n      Callback<R> callback,\n      int order) {\n    decodeHelper.init(\n        glideContext,\n        model,\n        signature,\n        width,\n        height,\n        diskCacheStrategy,\n        resourceClass,\n        transcodeClass,\n        priority,\n        options,\n        transformations,\n        isTransformationRequired,\n        isScaleOnlyOrNoTransform,\n        diskCacheProvider);\n    this.glideContext = glideContext;\n    this.signature = signature;\n    this.priority = priority;\n    this.loadKey = loadKey;\n    this.width = width;\n    this.height = height;\n    this.diskCacheStrategy = diskCacheStrategy;\n    this.onlyRetrieveFromCache = onlyRetrieveFromCache;\n    this.options = options;\n    this.callback = callback;\n    this.order = order;\n    this.runReason = RunReason.INITIALIZE;\n    this.model = model;\n    this.experiments = glideContext.getExperiments();\n    this.glideThreadPriorityOverride = options.get(GLIDE_THREAD_PRIORITY_OVERRIDE);\n    return this;\n  }\n\n  /**\n   * Returns true if this job will attempt to decode a resource from the disk cache, and false if it\n   * will always decode from source.\n   */\n  boolean willDecodeFromCache() {\n    Stage firstStage = getNextStage(Stage.INITIALIZE);\n    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;\n  }\n\n  /**\n   * Called when this object is no longer in use externally.\n   *\n   * @param isRemovedFromQueue {@code true} if we've been removed from the queue and {@link #run} is\n   *     neither in progress nor will ever be called again.\n   */\n  void release(boolean isRemovedFromQueue) {\n    if (releaseManager.release(isRemovedFromQueue)) {\n      releaseInternal();\n    }\n  }\n\n  /**\n   * Called when we've finished encoding (either because the encode process is complete, or because\n   * we don't have anything to encode).\n   */\n  private void onEncodeComplete() {\n    if (releaseManager.onEncodeComplete()) {\n      releaseInternal();\n    }\n  }\n\n  /** Called when the load has failed due to a an error or a series of errors. */\n  private void onLoadFailed() {\n    if (releaseManager.onFailed()) {\n      releaseInternal();\n    }\n  }\n\n  private void releaseInternal() {\n    releaseManager.reset();\n    deferredEncodeManager.clear();\n    decodeHelper.clear();\n    isCallbackNotified = false;\n    glideContext = null;\n    signature = null;\n    options = null;\n    priority = null;\n    loadKey = null;\n    callback = null;\n    stage = null;\n    currentGenerator = null;\n    currentThread = null;\n    currentSourceKey = null;\n    currentData = null;\n    currentDataSource = null;\n    currentFetcher = null;\n    startFetchTime = 0L;\n    isCancelled = false;\n    model = null;\n    throwables.clear();\n    pool.release(this);\n  }\n\n  @Override\n  public int compareTo(@NonNull DecodeJob<?> other) {\n    int result = getPriority() - other.getPriority();\n    if (result == 0) {\n      result = order - other.order;\n    }\n    return result;\n  }\n\n  private int getPriority() {\n    return priority.ordinal();\n  }\n\n  public void cancel() {\n    isCancelled = true;\n    DataFetcherGenerator local = currentGenerator;\n    if (local != null) {\n      local.cancel();\n    }\n  }\n\n  // We need to rethrow only CallbackException, but not other types of Throwables.\n  @SuppressWarnings(\"PMD.AvoidRethrowingException\")\n  @Override\n  public void run() {\n    // This should be much more fine grained, but since Java's thread pool implementation silently\n    // swallows all otherwise fatal exceptions, this will at least make it obvious to developers\n    // that something is failing.\n    GlideTrace.beginSectionFormat(\"DecodeJob#run(reason=%s, model=%s)\", runReason, model);\n    // Methods in the try statement can invalidate currentFetcher, so set a local variable here to\n    // ensure that the fetcher is cleaned up either way.\n    DataFetcher<?> localFetcher = currentFetcher;\n    try {\n      if (isCancelled) {\n        notifyFailed();\n        return;\n      }\n      runWrapped();\n    } catch (CallbackException e) {\n      // If a callback not controlled by Glide throws an exception, we should avoid the Glide\n      // specific debug logic below.\n      throw e;\n    } catch (Throwable t) {\n      // Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our\n      // usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We\n      // are however ensuring that our callbacks are always notified when a load fails. Without this\n      // notification, uncaught throwables never notify the corresponding callbacks, which can cause\n      // loads to silently hang forever, a case that's especially bad for users using Futures on\n      // background threads.\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"DecodeJob threw unexpectedly\" + \", isCancelled: \" + isCancelled + \", stage: \" + stage,\n            t);\n      }\n      // When we're encoding we've already notified our callback and it isn't safe to do so again.\n      if (stage != Stage.ENCODE) {\n        throwables.add(t);\n        notifyFailed();\n      }\n      if (!isCancelled) {\n        throw t;\n      }\n      throw t;\n    } finally {\n      // Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call\n      // close in all cases anyway.\n      if (localFetcher != null) {\n        localFetcher.cleanup();\n      }\n      GlideTrace.endSection();\n    }\n  }\n\n  private void runWrapped() {\n    switch (runReason) {\n      case INITIALIZE:\n        stage = getNextStage(Stage.INITIALIZE);\n        currentGenerator = getNextGenerator();\n        runGenerators();\n        break;\n      case SWITCH_TO_SOURCE_SERVICE:\n        runGenerators();\n        break;\n      case DECODE_DATA:\n        decodeFromRetrievedData();\n        break;\n      default:\n        throw new IllegalStateException(\"Unrecognized run reason: \" + runReason);\n    }\n  }\n\n  private DataFetcherGenerator getNextGenerator() {\n    switch (stage) {\n      case RESOURCE_CACHE:\n        return new ResourceCacheGenerator(decodeHelper, this);\n      case DATA_CACHE:\n        return new DataCacheGenerator(decodeHelper, this);\n      case SOURCE:\n        return new SourceGenerator(decodeHelper, this);\n      case FINISHED:\n        return null;\n      default:\n        throw new IllegalStateException(\"Unrecognized stage: \" + stage);\n    }\n  }\n\n  private void runGenerators() {\n    currentThread = Thread.currentThread();\n    startFetchTime = LogTime.getLogTime();\n    boolean isStarted = false;\n    while (!isCancelled\n        && currentGenerator != null\n        && !(isStarted = currentGenerator.startNext())) {\n      stage = getNextStage(stage);\n      currentGenerator = getNextGenerator();\n\n      if (stage == Stage.SOURCE) {\n        reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE);\n        return;\n      }\n    }\n    // We've run out of stages and generators, give up.\n    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {\n      notifyFailed();\n    }\n\n    // Otherwise a generator started a new load and we expect to be called back in\n    // onDataFetcherReady.\n  }\n\n  /**\n   * Restores the OS priority of the Glide thread to the default thread priority of {@link\n   * com.bumptech.glide.load.engine.executor.GlideExecutor}.\n   */\n  private void restoreThreadPriority() {\n    if (!experiments.isEnabled(OverrideGlideThreadPriority.class)) {\n      throw new IllegalStateException(\"OverrideGlideThreadPriority experiment is not enabled.\");\n    }\n    if (glideThreadPriorityOverride != null && glideThreadPriorityOverride.get() != null) {\n      try {\n        Process.setThreadPriority(Process.myTid(), GlideExecutor.DEFAULT_PRIORITY);\n      } catch (IllegalArgumentException | SecurityException e) {\n        glideThreadPriorityOverride = null;\n        if (Log.isLoggable(TAG, Log.VERBOSE)) {\n          Log.v(\n              TAG,\n              \"Failed to set thread priority; using default priority for any subsequent jobs.\",\n              e);\n        }\n      }\n    }\n  }\n\n  private void notifyFailed() {\n    if (experiments.isEnabled(OverrideGlideThreadPriority.class)) {\n      restoreThreadPriority();\n    }\n    setNotifiedOrThrow();\n    GlideException e = new GlideException(\"Failed to load resource\", new ArrayList<>(throwables));\n    callback.onLoadFailed(e);\n    onLoadFailed();\n  }\n\n  private void notifyComplete(\n      Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {\n    if (experiments.isEnabled(OverrideGlideThreadPriority.class)) {\n      restoreThreadPriority();\n    }\n    setNotifiedOrThrow();\n    callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey);\n  }\n\n  private void setNotifiedOrThrow() {\n    stateVerifier.throwIfRecycled();\n    if (isCallbackNotified) {\n      Throwable lastThrown = throwables.isEmpty() ? null : throwables.get(throwables.size() - 1);\n      throw new IllegalStateException(\"Already notified\", lastThrown);\n    }\n    isCallbackNotified = true;\n  }\n\n  private Stage getNextStage(Stage current) {\n    switch (current) {\n      case INITIALIZE:\n        return diskCacheStrategy.decodeCachedResource()\n            ? Stage.RESOURCE_CACHE\n            : getNextStage(Stage.RESOURCE_CACHE);\n      case RESOURCE_CACHE:\n        return diskCacheStrategy.decodeCachedData()\n            ? Stage.DATA_CACHE\n            : getNextStage(Stage.DATA_CACHE);\n      case DATA_CACHE:\n        // Skip loading from source if the user opted to only retrieve the resource from cache.\n        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;\n      case SOURCE:\n      case FINISHED:\n        return Stage.FINISHED;\n      default:\n        throw new IllegalArgumentException(\"Unrecognized stage: \" + current);\n    }\n  }\n\n  private void reschedule(RunReason runReason) {\n    this.runReason = runReason;\n    callback.reschedule(this);\n  }\n\n  // This is used by SourceGenerator to ask us to switch back to our thread. Internal methods in\n  // this class should call reschedule with a specific RunReason.\n  @Override\n  public void reschedule() {\n    reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE);\n  }\n\n  @Override\n  public void onDataFetcherReady(\n      Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {\n    this.currentSourceKey = sourceKey;\n    this.currentData = data;\n    this.currentFetcher = fetcher;\n    this.currentDataSource = dataSource;\n    this.currentAttemptingKey = attemptedKey;\n    this.isLoadingFromAlternateCacheKey = sourceKey != decodeHelper.getCacheKeys().get(0);\n\n    if (Thread.currentThread() != currentThread) {\n      reschedule(RunReason.DECODE_DATA);\n    } else {\n      GlideTrace.beginSection(\"DecodeJob.decodeFromRetrievedData\");\n      try {\n        decodeFromRetrievedData();\n      } finally {\n        GlideTrace.endSection();\n      }\n    }\n  }\n\n  @Override\n  public void onDataFetcherFailed(\n      Key attemptedKey, Exception e, DataFetcher<?> fetcher, DataSource dataSource) {\n    fetcher.cleanup();\n    GlideException exception = new GlideException(\"Fetching data failed\", e);\n    exception.setLoggingDetails(attemptedKey, dataSource, fetcher.getDataClass());\n    throwables.add(exception);\n    if (Thread.currentThread() != currentThread) {\n      reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE);\n    } else {\n      runGenerators();\n    }\n  }\n\n  private void decodeFromRetrievedData() {\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      logWithTimeAndKey(\n          \"Retrieved data\",\n          startFetchTime,\n          \"data: \"\n              + currentData\n              + \", cache key: \"\n              + currentSourceKey\n              + \", fetcher: \"\n              + currentFetcher);\n    }\n    if (experiments.isEnabled(OverrideGlideThreadPriority.class)\n        && glideThreadPriorityOverride != null\n        && glideThreadPriorityOverride.get() != null) {\n      try {\n        Process.setThreadPriority(Process.myTid(), glideThreadPriorityOverride.get().intValue());\n      } catch (IllegalArgumentException | SecurityException e) {\n        glideThreadPriorityOverride = null;\n        if (Log.isLoggable(TAG, Log.VERBOSE)) {\n          Log.v(\n              TAG,\n              \"Failed to set thread priority; using default priority for any subsequent jobs.\",\n              e);\n        }\n      }\n    }\n    Resource<R> resource = null;\n    try {\n      resource = decodeFromData(currentFetcher, currentData, currentDataSource);\n    } catch (GlideException e) {\n      e.setLoggingDetails(currentAttemptingKey, currentDataSource);\n      throwables.add(e);\n    }\n    if (resource != null) {\n      notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);\n    } else {\n      runGenerators();\n    }\n  }\n\n  private void notifyEncodeAndRelease(\n      Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {\n    GlideTrace.beginSection(\"DecodeJob.notifyEncodeAndRelease\");\n    try {\n      if (resource instanceof Initializable) {\n        ((Initializable) resource).initialize();\n      }\n\n      Resource<R> result = resource;\n      LockedResource<R> lockedResource = null;\n      if (deferredEncodeManager.hasResourceToEncode()) {\n        lockedResource = LockedResource.obtain(resource);\n        result = lockedResource;\n      }\n\n      notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey);\n\n      stage = Stage.ENCODE;\n      try {\n        if (deferredEncodeManager.hasResourceToEncode()) {\n          deferredEncodeManager.encode(diskCacheProvider, options);\n        }\n      } finally {\n        if (lockedResource != null) {\n          lockedResource.unlock();\n        }\n      }\n      // Call onEncodeComplete outside the finally block so that it's not called if the encode\n      // process\n      // throws.\n      onEncodeComplete();\n    } finally {\n      GlideTrace.endSection();\n    }\n  }\n\n  private <Data> Resource<R> decodeFromData(\n      DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException {\n    try {\n      if (data == null) {\n        return null;\n      }\n      long startTime = LogTime.getLogTime();\n      Resource<R> result = decodeFromFetcher(data, dataSource);\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        logWithTimeAndKey(\"Decoded result \" + result, startTime);\n      }\n      return result;\n    } finally {\n      fetcher.cleanup();\n    }\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)\n      throws GlideException {\n    LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());\n    return runLoadPath(data, dataSource, path);\n  }\n\n  @NonNull\n  private Options getOptionsWithHardwareConfig(DataSource dataSource) {\n    Options options = this.options;\n    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n      return options;\n    }\n\n    boolean isHardwareConfigSafe =\n        dataSource == DataSource.RESOURCE_DISK_CACHE || decodeHelper.isScaleOnlyOrNoTransform();\n    Boolean isHardwareConfigAllowed = options.get(Downsampler.ALLOW_HARDWARE_CONFIG);\n\n    // If allow hardware config is defined, we can use it if it's set to false or if it's safe to\n    // use the hardware config for the request.\n    if (isHardwareConfigAllowed != null && (!isHardwareConfigAllowed || isHardwareConfigSafe)) {\n      return options;\n    }\n\n    // If allow hardware config is undefined or is set to true but it's unsafe for us to use the\n    // hardware config for this request, we need to override the config.\n    options = new Options();\n    options.putAll(this.options);\n    options.set(Downsampler.ALLOW_HARDWARE_CONFIG, isHardwareConfigSafe);\n\n    return options;\n  }\n\n  private <Data, ResourceType> Resource<R> runLoadPath(\n      Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path)\n      throws GlideException {\n    Options options = getOptionsWithHardwareConfig(dataSource);\n    DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);\n    try {\n      // ResourceType in DecodeCallback below is required for compilation to work with gradle.\n      return path.load(\n          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));\n    } finally {\n      rewinder.cleanup();\n    }\n  }\n\n  private void logWithTimeAndKey(String message, long startTime) {\n    logWithTimeAndKey(message, startTime, null /*extraArgs*/);\n  }\n\n  private void logWithTimeAndKey(String message, long startTime, String extraArgs) {\n    Log.v(\n        TAG,\n        message\n            + \" in \"\n            + LogTime.getElapsedMillis(startTime)\n            + \", load key: \"\n            + loadKey\n            + (extraArgs != null ? \", \" + extraArgs : \"\")\n            + \", thread: \"\n            + Thread.currentThread().getName());\n  }\n\n  @NonNull\n  @Override\n  public StateVerifier getVerifier() {\n    return stateVerifier;\n  }\n\n  @Synthetic\n  @NonNull\n  <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {\n    @SuppressWarnings(\"unchecked\")\n    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();\n    Transformation<Z> appliedTransformation = null;\n    Resource<Z> transformed = decoded;\n    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {\n      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);\n      transformed = appliedTransformation.transform(glideContext, decoded, width, height);\n    }\n    // TODO: Make this the responsibility of the Transformation.\n    if (!decoded.equals(transformed)) {\n      decoded.recycle();\n    }\n\n    final EncodeStrategy encodeStrategy;\n    final ResourceEncoder<Z> encoder;\n    if (decodeHelper.isResourceEncoderAvailable(transformed)) {\n      encoder = decodeHelper.getResultEncoder(transformed);\n      encodeStrategy = encoder.getEncodeStrategy(options);\n    } else {\n      encoder = null;\n      encodeStrategy = EncodeStrategy.NONE;\n    }\n\n    Resource<Z> result = transformed;\n    boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);\n    if (diskCacheStrategy.isResourceCacheable(\n        isFromAlternateCacheKey, dataSource, encodeStrategy)) {\n      if (encoder == null) {\n        throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());\n      }\n      final Key key;\n      switch (encodeStrategy) {\n        case SOURCE:\n          key = new DataCacheKey(currentSourceKey, signature);\n          break;\n        case TRANSFORMED:\n          key =\n              new ResourceCacheKey(\n                  decodeHelper.getArrayPool(),\n                  currentSourceKey,\n                  signature,\n                  width,\n                  height,\n                  appliedTransformation,\n                  resourceSubClass,\n                  options);\n          break;\n        default:\n          throw new IllegalArgumentException(\"Unknown strategy: \" + encodeStrategy);\n      }\n\n      LockedResource<Z> lockedResult = LockedResource.obtain(transformed);\n      deferredEncodeManager.init(key, encoder, lockedResult);\n      result = lockedResult;\n    }\n    return result;\n  }\n\n  private final class DecodeCallback<Z> implements DecodePath.DecodeCallback<Z> {\n\n    private final DataSource dataSource;\n\n    @Synthetic\n    DecodeCallback(DataSource dataSource) {\n      this.dataSource = dataSource;\n    }\n\n    @NonNull\n    @Override\n    public Resource<Z> onResourceDecoded(@NonNull Resource<Z> decoded) {\n      return DecodeJob.this.onResourceDecoded(dataSource, decoded);\n    }\n  }\n\n  /**\n   * Responsible for indicating when it is safe for the job to be cleared and returned to the pool.\n   */\n  private static class ReleaseManager {\n    private boolean isReleased;\n    private boolean isEncodeComplete;\n    private boolean isFailed;\n\n    @Synthetic\n    ReleaseManager() {}\n\n    synchronized boolean release(boolean isRemovedFromQueue) {\n      isReleased = true;\n      return isComplete(isRemovedFromQueue);\n    }\n\n    synchronized boolean onEncodeComplete() {\n      isEncodeComplete = true;\n      return isComplete(false /*isRemovedFromQueue*/);\n    }\n\n    synchronized boolean onFailed() {\n      isFailed = true;\n      return isComplete(false /*isRemovedFromQueue*/);\n    }\n\n    synchronized void reset() {\n      isEncodeComplete = false;\n      isReleased = false;\n      isFailed = false;\n    }\n\n    private boolean isComplete(boolean isRemovedFromQueue) {\n      return (isFailed || isRemovedFromQueue || isEncodeComplete) && isReleased;\n    }\n  }\n\n  /**\n   * Allows transformed resources to be encoded after the transcoded result is already delivered to\n   * requestors.\n   */\n  private static class DeferredEncodeManager<Z> {\n    private Key key;\n    private ResourceEncoder<Z> encoder;\n    private LockedResource<Z> toEncode;\n\n    @Synthetic\n    DeferredEncodeManager() {}\n\n    // We just need the encoder and resource type to match, which this will enforce.\n    @SuppressWarnings(\"unchecked\")\n    <X> void init(Key key, ResourceEncoder<X> encoder, LockedResource<X> toEncode) {\n      this.key = key;\n      this.encoder = (ResourceEncoder<Z>) encoder;\n      this.toEncode = (LockedResource<Z>) toEncode;\n    }\n\n    void encode(DiskCacheProvider diskCacheProvider, Options options) {\n      GlideTrace.beginSection(\"DecodeJob.encode\");\n      try {\n        diskCacheProvider\n            .getDiskCache()\n            .put(key, new DataCacheWriter<>(encoder, toEncode, options));\n      } finally {\n        toEncode.unlock();\n        GlideTrace.endSection();\n      }\n    }\n\n    boolean hasResourceToEncode() {\n      return toEncode != null;\n    }\n\n    void clear() {\n      key = null;\n      encoder = null;\n      toEncode = null;\n    }\n  }\n\n  interface Callback<R> {\n\n    void onResourceReady(\n        Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey);\n\n    void onLoadFailed(GlideException e);\n\n    void reschedule(DecodeJob<?> job);\n  }\n\n  interface DiskCacheProvider {\n    DiskCache getDiskCache();\n  }\n\n  /** Why we're being executed again. */\n  private enum RunReason {\n    /** The first time we've been submitted. */\n    INITIALIZE,\n    /** We want to switch from the disk cache service to the source executor. */\n    SWITCH_TO_SOURCE_SERVICE,\n    /**\n     * We retrieved some data on a thread we don't own and want to switch back to our thread to\n     * process the data.\n     */\n    DECODE_DATA,\n  }\n\n  /** Where we're trying to decode data from. */\n  private enum Stage {\n    /** The initial stage. */\n    INITIALIZE,\n    /** Decode from a cached resource. */\n    RESOURCE_CACHE,\n    /** Decode from cached source data. */\n    DATA_CACHE,\n    /** Decode from retrieved source. */\n    SOURCE,\n    /** Encoding transformed resources after a successful load. */\n    ENCODE,\n    /** No more viable stages. */\n    FINISHED,\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/DecodePath.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.core.util.Pools.Pool;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.data.DataRewinder;\nimport com.bumptech.glide.load.resource.transcode.ResourceTranscoder;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Attempts to decode and transcode resource type from a given data type.\n *\n * @param <DataType> The type of data ResourceType that will be decoded from.\n * @param <ResourceType> The type of intermediate resource that will be decoded.\n * @param <Transcode> The final type of resource that will be transcoded from ResourceType and\n *     returned to the caller.\n */\npublic class DecodePath<DataType, ResourceType, Transcode> {\n  private static final String TAG = \"DecodePath\";\n  private final Class<DataType> dataClass;\n  private final List<? extends ResourceDecoder<DataType, ResourceType>> decoders;\n  private final ResourceTranscoder<ResourceType, Transcode> transcoder;\n  private final Pool<List<Throwable>> listPool;\n  private final String failureMessage;\n\n  public DecodePath(\n      Class<DataType> dataClass,\n      Class<ResourceType> resourceClass,\n      Class<Transcode> transcodeClass,\n      List<? extends ResourceDecoder<DataType, ResourceType>> decoders,\n      ResourceTranscoder<ResourceType, Transcode> transcoder,\n      Pool<List<Throwable>> listPool) {\n    this.dataClass = dataClass;\n    this.decoders = decoders;\n    this.transcoder = transcoder;\n    this.listPool = listPool;\n    failureMessage =\n        \"Failed DecodePath{\"\n            + dataClass.getSimpleName()\n            + \"->\"\n            + resourceClass.getSimpleName()\n            + \"->\"\n            + transcodeClass.getSimpleName()\n            + \"}\";\n  }\n\n  public Resource<Transcode> decode(\n      DataRewinder<DataType> rewinder,\n      int width,\n      int height,\n      @NonNull Options options,\n      DecodeCallback<ResourceType> callback)\n      throws GlideException {\n    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);\n    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);\n    return transcoder.transcode(transformed, options);\n  }\n\n  @NonNull\n  private Resource<ResourceType> decodeResource(\n      DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options)\n      throws GlideException {\n    List<Throwable> exceptions = Preconditions.checkNotNull(listPool.acquire());\n    try {\n      return decodeResourceWithList(rewinder, width, height, options, exceptions);\n    } finally {\n      listPool.release(exceptions);\n    }\n  }\n\n  @NonNull\n  private Resource<ResourceType> decodeResourceWithList(\n      DataRewinder<DataType> rewinder,\n      int width,\n      int height,\n      @NonNull Options options,\n      List<Throwable> exceptions)\n      throws GlideException {\n    Resource<ResourceType> result = null;\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0, size = decoders.size(); i < size; i++) {\n      ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);\n      try {\n        DataType data = rewinder.rewindAndGet();\n        if (decoder.handles(data, options)) {\n          data = rewinder.rewindAndGet();\n          result = decoder.decode(data, width, height, options);\n        }\n        // Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but\n        // instead log and continue. See #2406 for an example.\n      } catch (IOException | RuntimeException | OutOfMemoryError e) {\n        if (Log.isLoggable(TAG, Log.VERBOSE)) {\n          Log.v(TAG, \"Failed to decode data for \" + decoder, e);\n        }\n        exceptions.add(e);\n      }\n\n      if (result != null) {\n        break;\n      }\n    }\n\n    if (result == null) {\n      throw new GlideException(failureMessage, new ArrayList<>(exceptions));\n    }\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return \"DecodePath{\"\n        + \" dataClass=\"\n        + dataClass\n        + \", decoders=\"\n        + decoders\n        + \", transcoder=\"\n        + transcoder\n        + '}';\n  }\n\n  interface DecodeCallback<ResourceType> {\n    @NonNull\n    Resource<ResourceType> onResourceDecoded(@NonNull Resource<ResourceType> resource);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/DiskCacheStrategy.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.EncodeStrategy;\n\n/** Set of available caching strategies for media. */\npublic abstract class DiskCacheStrategy {\n\n  /**\n   * Caches remote data with both {@link #DATA} and {@link #RESOURCE}, and local data with {@link\n   * #RESOURCE} only.\n   */\n  public static final DiskCacheStrategy ALL =\n      new DiskCacheStrategy() {\n        @Override\n        public boolean isDataCacheable(DataSource dataSource) {\n          return dataSource == DataSource.REMOTE;\n        }\n\n        @Override\n        public boolean isResourceCacheable(\n            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {\n          return dataSource != DataSource.RESOURCE_DISK_CACHE\n              && dataSource != DataSource.MEMORY_CACHE;\n        }\n\n        @Override\n        public boolean decodeCachedResource() {\n          return true;\n        }\n\n        @Override\n        public boolean decodeCachedData() {\n          return true;\n        }\n      };\n\n  /** Saves no data to cache. */\n  public static final DiskCacheStrategy NONE =\n      new DiskCacheStrategy() {\n        @Override\n        public boolean isDataCacheable(DataSource dataSource) {\n          return false;\n        }\n\n        @Override\n        public boolean isResourceCacheable(\n            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {\n          return false;\n        }\n\n        @Override\n        public boolean decodeCachedResource() {\n          return false;\n        }\n\n        @Override\n        public boolean decodeCachedData() {\n          return false;\n        }\n      };\n\n  /** Writes retrieved data directly to the disk cache before it's decoded. */\n  public static final DiskCacheStrategy DATA =\n      new DiskCacheStrategy() {\n        @Override\n        public boolean isDataCacheable(DataSource dataSource) {\n          return dataSource != DataSource.DATA_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;\n        }\n\n        @Override\n        public boolean isResourceCacheable(\n            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {\n          return false;\n        }\n\n        @Override\n        public boolean decodeCachedResource() {\n          return false;\n        }\n\n        @Override\n        public boolean decodeCachedData() {\n          return true;\n        }\n      };\n\n  /** Writes resources to disk after they've been decoded. */\n  public static final DiskCacheStrategy RESOURCE =\n      new DiskCacheStrategy() {\n        @Override\n        public boolean isDataCacheable(DataSource dataSource) {\n          return false;\n        }\n\n        @Override\n        public boolean isResourceCacheable(\n            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {\n          return dataSource != DataSource.RESOURCE_DISK_CACHE\n              && dataSource != DataSource.MEMORY_CACHE;\n        }\n\n        @Override\n        public boolean decodeCachedResource() {\n          return true;\n        }\n\n        @Override\n        public boolean decodeCachedData() {\n          return false;\n        }\n      };\n\n  /**\n   * Tries to intelligently choose a strategy based on the data source of the {@link\n   * com.bumptech.glide.load.data.DataFetcher} and the {@link\n   * com.bumptech.glide.load.EncodeStrategy} of the {@link com.bumptech.glide.load.ResourceEncoder}\n   * (if an {@link com.bumptech.glide.load.ResourceEncoder} is available).\n   */\n  public static final DiskCacheStrategy AUTOMATIC =\n      new DiskCacheStrategy() {\n        @Override\n        public boolean isDataCacheable(DataSource dataSource) {\n          return dataSource == DataSource.REMOTE;\n        }\n\n        @SuppressWarnings(\"checkstyle:UnnecessaryParentheses\") // Readability\n        @Override\n        public boolean isResourceCacheable(\n            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {\n          return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)\n                  || dataSource == DataSource.LOCAL)\n              && encodeStrategy == EncodeStrategy.TRANSFORMED;\n        }\n\n        @Override\n        public boolean decodeCachedResource() {\n          return true;\n        }\n\n        @Override\n        public boolean decodeCachedData() {\n          return true;\n        }\n      };\n\n  /**\n   * Returns true if this request should cache the original unmodified data.\n   *\n   * @param dataSource Indicates where the data was originally retrieved.\n   */\n  public abstract boolean isDataCacheable(DataSource dataSource);\n\n  /**\n   * Returns true if this request should cache the final transformed resource.\n   *\n   * @param isFromAlternateCacheKey {@code true} if the resource we've decoded was loaded using an\n   *     alternative, rather than the primary, cache key.\n   * @param dataSource Indicates where the data used to decode the resource was originally\n   *     retrieved.\n   * @param encodeStrategy The {@link EncodeStrategy} the {@link\n   *     com.bumptech.glide.load.ResourceEncoder} will use to encode the resource.\n   */\n  public abstract boolean isResourceCacheable(\n      boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);\n\n  /** Returns true if this request should attempt to decode cached resource data. */\n  public abstract boolean decodeCachedResource();\n\n  /** Returns true if this request should attempt to decode cached source data. */\n  public abstract boolean decodeCachedData();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/Engine.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport androidx.core.util.Pools;\nimport com.bumptech.glide.GlideContext;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.EngineResource.ResourceListener;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.engine.cache.DiskCacheAdapter;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.request.ResourceCallback;\nimport com.bumptech.glide.util.Executors;\nimport com.bumptech.glide.util.LogTime;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.pool.FactoryPools;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\n/** Responsible for starting loads and managing active and cached resources. */\npublic class Engine\n    implements EngineJobListener,\n        MemoryCache.ResourceRemovedListener,\n        EngineResource.ResourceListener {\n  private static final String TAG = \"Engine\";\n  private static final int JOB_POOL_SIZE = 150;\n  private static final boolean VERBOSE_IS_LOGGABLE = Log.isLoggable(TAG, Log.VERBOSE);\n  private final Jobs jobs;\n  private final EngineKeyFactory keyFactory;\n  private final MemoryCache cache;\n  private final EngineJobFactory engineJobFactory;\n  private final ResourceRecycler resourceRecycler;\n  private final LazyDiskCacheProvider diskCacheProvider;\n  private final DecodeJobFactory decodeJobFactory;\n  private final ActiveResources activeResources;\n\n  public Engine(\n      MemoryCache memoryCache,\n      DiskCache.Factory diskCacheFactory,\n      GlideExecutor diskCacheExecutor,\n      GlideExecutor sourceExecutor,\n      GlideExecutor sourceUnlimitedExecutor,\n      GlideExecutor animationExecutor,\n      boolean isActiveResourceRetentionAllowed) {\n    this(\n        memoryCache,\n        diskCacheFactory,\n        diskCacheExecutor,\n        sourceExecutor,\n        sourceUnlimitedExecutor,\n        animationExecutor,\n        /* jobs= */ null,\n        /* keyFactory= */ null,\n        /* activeResources= */ null,\n        /* engineJobFactory= */ null,\n        /* decodeJobFactory= */ null,\n        /* resourceRecycler= */ null,\n        isActiveResourceRetentionAllowed);\n  }\n\n  @VisibleForTesting\n  Engine(\n      MemoryCache cache,\n      DiskCache.Factory diskCacheFactory,\n      GlideExecutor diskCacheExecutor,\n      GlideExecutor sourceExecutor,\n      GlideExecutor sourceUnlimitedExecutor,\n      GlideExecutor animationExecutor,\n      Jobs jobs,\n      EngineKeyFactory keyFactory,\n      ActiveResources activeResources,\n      EngineJobFactory engineJobFactory,\n      DecodeJobFactory decodeJobFactory,\n      ResourceRecycler resourceRecycler,\n      boolean isActiveResourceRetentionAllowed) {\n    this.cache = cache;\n    this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);\n\n    if (activeResources == null) {\n      activeResources = new ActiveResources(isActiveResourceRetentionAllowed);\n    }\n    this.activeResources = activeResources;\n    activeResources.setListener(this);\n\n    if (keyFactory == null) {\n      keyFactory = new EngineKeyFactory();\n    }\n    this.keyFactory = keyFactory;\n\n    if (jobs == null) {\n      jobs = new Jobs();\n    }\n    this.jobs = jobs;\n\n    if (engineJobFactory == null) {\n      engineJobFactory =\n          new EngineJobFactory(\n              diskCacheExecutor,\n              sourceExecutor,\n              sourceUnlimitedExecutor,\n              animationExecutor,\n              /* engineJobListener= */ this,\n              /* resourceListener= */ this);\n    }\n    this.engineJobFactory = engineJobFactory;\n\n    if (decodeJobFactory == null) {\n      decodeJobFactory = new DecodeJobFactory(diskCacheProvider);\n    }\n    this.decodeJobFactory = decodeJobFactory;\n\n    if (resourceRecycler == null) {\n      resourceRecycler = new ResourceRecycler();\n    }\n    this.resourceRecycler = resourceRecycler;\n\n    cache.setResourceRemovedListener(this);\n  }\n\n  /**\n   * Starts a load for the given arguments.\n   *\n   * <p>Must be called on the main thread.\n   *\n   * <p>The flow for any request is as follows:\n   *\n   * <ul>\n   *   <li>Check the current set of actively used resources, return the active resource if present,\n   *       and move any newly inactive resources into the memory cache.\n   *   <li>Check the memory cache and provide the cached resource if present.\n   *   <li>Check the current set of in progress loads and add the cb to the in progress load if one\n   *       is present.\n   *   <li>Start a new load.\n   * </ul>\n   *\n   * <p>Active resources are those that have been provided to at least one request and have not yet\n   * been released. Once all consumers of a resource have released that resource, the resource then\n   * goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to\n   * the active resources. If the resource is evicted from the cache, its resources are recycled and\n   * re-used if possible and the resource is discarded. There is no strict requirement that\n   * consumers release their resources so active resources are held weakly.\n   *\n   * @param width The target width in pixels of the desired resource.\n   * @param height The target height in pixels of the desired resource.\n   * @param cb The callback that will be called when the load completes.\n   */\n  public <R> LoadStatus load(\n      GlideContext glideContext,\n      Object model,\n      Key signature,\n      int width,\n      int height,\n      Class<?> resourceClass,\n      Class<R> transcodeClass,\n      Priority priority,\n      DiskCacheStrategy diskCacheStrategy,\n      Map<Class<?>, Transformation<?>> transformations,\n      boolean isTransformationRequired,\n      boolean isScaleOnlyOrNoTransform,\n      Options options,\n      boolean isMemoryCacheable,\n      boolean useUnlimitedSourceExecutorPool,\n      boolean useAnimationPool,\n      boolean onlyRetrieveFromCache,\n      ResourceCallback cb,\n      Executor callbackExecutor) {\n    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;\n\n    EngineKey key =\n        keyFactory.buildKey(\n            model,\n            signature,\n            width,\n            height,\n            transformations,\n            resourceClass,\n            transcodeClass,\n            options);\n\n    EngineResource<?> memoryResource;\n    synchronized (this) {\n      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);\n\n      if (memoryResource == null) {\n        return waitForExistingOrStartNewJob(\n            glideContext,\n            model,\n            signature,\n            width,\n            height,\n            resourceClass,\n            transcodeClass,\n            priority,\n            diskCacheStrategy,\n            transformations,\n            isTransformationRequired,\n            isScaleOnlyOrNoTransform,\n            options,\n            isMemoryCacheable,\n            useUnlimitedSourceExecutorPool,\n            useAnimationPool,\n            onlyRetrieveFromCache,\n            cb,\n            callbackExecutor,\n            key,\n            startTime);\n      }\n    }\n\n    // Avoid calling back while holding the engine lock, doing so makes it easier for callers to\n    // deadlock.\n    cb.onResourceReady(\n        memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n    return null;\n  }\n\n  private <R> LoadStatus waitForExistingOrStartNewJob(\n      GlideContext glideContext,\n      Object model,\n      Key signature,\n      int width,\n      int height,\n      Class<?> resourceClass,\n      Class<R> transcodeClass,\n      Priority priority,\n      DiskCacheStrategy diskCacheStrategy,\n      Map<Class<?>, Transformation<?>> transformations,\n      boolean isTransformationRequired,\n      boolean isScaleOnlyOrNoTransform,\n      Options options,\n      boolean isMemoryCacheable,\n      boolean useUnlimitedSourceExecutorPool,\n      boolean useAnimationPool,\n      boolean onlyRetrieveFromCache,\n      ResourceCallback cb,\n      Executor callbackExecutor,\n      EngineKey key,\n      long startTime) {\n\n    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);\n    if (current != null) {\n      current.addCallback(cb, callbackExecutor);\n      if (VERBOSE_IS_LOGGABLE) {\n        logWithTimeAndKey(\"Added to existing load\", startTime, key);\n      }\n      return new LoadStatus(cb, current);\n    }\n\n    EngineJob<R> engineJob =\n        engineJobFactory.build(\n            key,\n            isMemoryCacheable,\n            useUnlimitedSourceExecutorPool,\n            useAnimationPool,\n            onlyRetrieveFromCache);\n\n    DecodeJob<R> decodeJob =\n        decodeJobFactory.build(\n            glideContext,\n            model,\n            key,\n            signature,\n            width,\n            height,\n            resourceClass,\n            transcodeClass,\n            priority,\n            diskCacheStrategy,\n            transformations,\n            isTransformationRequired,\n            isScaleOnlyOrNoTransform,\n            onlyRetrieveFromCache,\n            options,\n            engineJob);\n\n    jobs.put(key, engineJob);\n\n    engineJob.addCallback(cb, callbackExecutor);\n    engineJob.start(decodeJob);\n\n    if (VERBOSE_IS_LOGGABLE) {\n      logWithTimeAndKey(\"Started new load\", startTime, key);\n    }\n    return new LoadStatus(cb, engineJob);\n  }\n\n  @Nullable\n  private EngineResource<?> loadFromMemory(\n      EngineKey key, boolean isMemoryCacheable, long startTime) {\n    if (!isMemoryCacheable) {\n      return null;\n    }\n\n    EngineResource<?> active = loadFromActiveResources(key);\n    if (active != null) {\n      if (VERBOSE_IS_LOGGABLE) {\n        logWithTimeAndKey(\"Loaded resource from active resources\", startTime, key);\n      }\n      return active;\n    }\n\n    EngineResource<?> cached = loadFromCache(key);\n    if (cached != null) {\n      if (VERBOSE_IS_LOGGABLE) {\n        logWithTimeAndKey(\"Loaded resource from cache\", startTime, key);\n      }\n      return cached;\n    }\n\n    return null;\n  }\n\n  private static void logWithTimeAndKey(String log, long startTime, Key key) {\n    Log.v(TAG, log + \" in \" + LogTime.getElapsedMillis(startTime) + \"ms, key: \" + key);\n  }\n\n  @Nullable\n  private EngineResource<?> loadFromActiveResources(Key key) {\n    EngineResource<?> active = activeResources.get(key);\n    if (active != null) {\n      active.acquire();\n    }\n\n    return active;\n  }\n\n  private EngineResource<?> loadFromCache(Key key) {\n    EngineResource<?> cached = getEngineResourceFromCache(key);\n    if (cached != null) {\n      cached.acquire();\n      activeResources.activate(key, cached);\n    }\n    return cached;\n  }\n\n  private EngineResource<?> getEngineResourceFromCache(Key key) {\n    Resource<?> cached = cache.remove(key);\n\n    final EngineResource<?> result;\n    if (cached == null) {\n      result = null;\n    } else if (cached instanceof EngineResource) {\n      // Save an object allocation if we've cached an EngineResource (the typical case).\n      result = (EngineResource<?>) cached;\n    } else {\n      result =\n          new EngineResource<>(\n              cached,\n              /* isMemoryCacheable= */ true,\n              /* isRecyclable= */ true,\n              key,\n              /* listener= */ this);\n    }\n    return result;\n  }\n\n  public void release(Resource<?> resource) {\n    if (resource instanceof EngineResource) {\n      ((EngineResource<?>) resource).release();\n    } else {\n      throw new IllegalArgumentException(\"Cannot release anything but an EngineResource\");\n    }\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public synchronized void onEngineJobComplete(\n      EngineJob<?> engineJob, Key key, EngineResource<?> resource) {\n    // A null resource indicates that the load failed, usually due to an exception.\n    if (resource != null && resource.isMemoryCacheable()) {\n      activeResources.activate(key, resource);\n    }\n\n    jobs.removeIfCurrent(key, engineJob);\n  }\n\n  @Override\n  public synchronized void onEngineJobCancelled(EngineJob<?> engineJob, Key key) {\n    jobs.removeIfCurrent(key, engineJob);\n  }\n\n  @Override\n  public void onResourceRemoved(@NonNull final Resource<?> resource) {\n    // Avoid deadlock with RequestManagers when recycling triggers recursive clear() calls.\n    // See b/145519760.\n    resourceRecycler.recycle(resource, /* forceNextFrame= */ true);\n  }\n\n  @Override\n  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {\n    activeResources.deactivate(cacheKey);\n    if (resource.isMemoryCacheable()) {\n      cache.put(cacheKey, resource);\n    } else {\n      resourceRecycler.recycle(resource, /* forceNextFrame= */ false);\n    }\n  }\n\n  public void clearDiskCache() {\n    diskCacheProvider.getDiskCache().clear();\n  }\n\n  @VisibleForTesting\n  public void shutdown() {\n    engineJobFactory.shutdown();\n    diskCacheProvider.clearDiskCacheIfCreated();\n    activeResources.shutdown();\n  }\n\n  /**\n   * Allows a request to indicate it no longer is interested in a given load.\n   *\n   * <p>Non-final for mocking.\n   */\n  public class LoadStatus {\n    private final EngineJob<?> engineJob;\n    private final ResourceCallback cb;\n\n    LoadStatus(ResourceCallback cb, EngineJob<?> engineJob) {\n      this.cb = cb;\n      this.engineJob = engineJob;\n    }\n\n    public void cancel() {\n      // Acquire the Engine lock so that a new request can't get access to a particular EngineJob\n      // just after the EngineJob has been cancelled. Without this lock, we'd allow new requests\n      // to find the cancelling EngineJob in our Jobs data structure. With this lock, the EngineJob\n      // is both cancelled and removed from Jobs atomically.\n      synchronized (Engine.this) {\n        engineJob.removeCallback(cb);\n      }\n    }\n  }\n\n  private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {\n\n    private final DiskCache.Factory factory;\n    private volatile DiskCache diskCache;\n\n    LazyDiskCacheProvider(DiskCache.Factory factory) {\n      this.factory = factory;\n    }\n\n    @VisibleForTesting\n    synchronized void clearDiskCacheIfCreated() {\n      if (diskCache == null) {\n        return;\n      }\n      diskCache.clear();\n    }\n\n    @Override\n    public DiskCache getDiskCache() {\n      if (diskCache == null) {\n        synchronized (this) {\n          if (diskCache == null) {\n            diskCache = factory.build();\n          }\n          if (diskCache == null) {\n            diskCache = new DiskCacheAdapter();\n          }\n        }\n      }\n      return diskCache;\n    }\n  }\n\n  @VisibleForTesting\n  static class DecodeJobFactory {\n    @Synthetic final DecodeJob.DiskCacheProvider diskCacheProvider;\n\n    @Synthetic\n    final Pools.Pool<DecodeJob<?>> pool =\n        FactoryPools.threadSafe(\n            JOB_POOL_SIZE,\n            new FactoryPools.Factory<DecodeJob<?>>() {\n              @Override\n              public DecodeJob<?> create() {\n                return new DecodeJob<>(diskCacheProvider, pool);\n              }\n            });\n\n    private int creationOrder;\n\n    DecodeJobFactory(DecodeJob.DiskCacheProvider diskCacheProvider) {\n      this.diskCacheProvider = diskCacheProvider;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    <R> DecodeJob<R> build(\n        GlideContext glideContext,\n        Object model,\n        EngineKey loadKey,\n        Key signature,\n        int width,\n        int height,\n        Class<?> resourceClass,\n        Class<R> transcodeClass,\n        Priority priority,\n        DiskCacheStrategy diskCacheStrategy,\n        Map<Class<?>, Transformation<?>> transformations,\n        boolean isTransformationRequired,\n        boolean isScaleOnlyOrNoTransform,\n        boolean onlyRetrieveFromCache,\n        Options options,\n        DecodeJob.Callback<R> callback) {\n      DecodeJob<R> result = Preconditions.checkNotNull((DecodeJob<R>) pool.acquire());\n      return result.init(\n          glideContext,\n          model,\n          loadKey,\n          signature,\n          width,\n          height,\n          resourceClass,\n          transcodeClass,\n          priority,\n          diskCacheStrategy,\n          transformations,\n          isTransformationRequired,\n          isScaleOnlyOrNoTransform,\n          onlyRetrieveFromCache,\n          options,\n          callback,\n          creationOrder++);\n    }\n  }\n\n  @VisibleForTesting\n  static class EngineJobFactory {\n    @Synthetic final GlideExecutor diskCacheExecutor;\n    @Synthetic final GlideExecutor sourceExecutor;\n    @Synthetic final GlideExecutor sourceUnlimitedExecutor;\n    @Synthetic final GlideExecutor animationExecutor;\n    @Synthetic final EngineJobListener engineJobListener;\n    @Synthetic final ResourceListener resourceListener;\n\n    @Synthetic\n    final Pools.Pool<EngineJob<?>> pool =\n        FactoryPools.threadSafe(\n            JOB_POOL_SIZE,\n            new FactoryPools.Factory<EngineJob<?>>() {\n              @Override\n              public EngineJob<?> create() {\n                return new EngineJob<>(\n                    diskCacheExecutor,\n                    sourceExecutor,\n                    sourceUnlimitedExecutor,\n                    animationExecutor,\n                    engineJobListener,\n                    resourceListener,\n                    pool);\n              }\n            });\n\n    EngineJobFactory(\n        GlideExecutor diskCacheExecutor,\n        GlideExecutor sourceExecutor,\n        GlideExecutor sourceUnlimitedExecutor,\n        GlideExecutor animationExecutor,\n        EngineJobListener engineJobListener,\n        ResourceListener resourceListener) {\n      this.diskCacheExecutor = diskCacheExecutor;\n      this.sourceExecutor = sourceExecutor;\n      this.sourceUnlimitedExecutor = sourceUnlimitedExecutor;\n      this.animationExecutor = animationExecutor;\n      this.engineJobListener = engineJobListener;\n      this.resourceListener = resourceListener;\n    }\n\n    @VisibleForTesting\n    void shutdown() {\n      Executors.shutdownAndAwaitTermination(diskCacheExecutor);\n      Executors.shutdownAndAwaitTermination(sourceExecutor);\n      Executors.shutdownAndAwaitTermination(sourceUnlimitedExecutor);\n      Executors.shutdownAndAwaitTermination(animationExecutor);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    <R> EngineJob<R> build(\n        Key key,\n        boolean isMemoryCacheable,\n        boolean useUnlimitedSourceGeneratorPool,\n        boolean useAnimationPool,\n        boolean onlyRetrieveFromCache) {\n      EngineJob<R> result = Preconditions.checkNotNull((EngineJob<R>) pool.acquire());\n      return result.init(\n          key,\n          isMemoryCacheable,\n          useUnlimitedSourceGeneratorPool,\n          useAnimationPool,\n          onlyRetrieveFromCache);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/EngineJob.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport androidx.core.util.Pools;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.EngineResource.ResourceListener;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.request.ResourceCallback;\nimport com.bumptech.glide.util.Executors;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.pool.FactoryPools.Poolable;\nimport com.bumptech.glide.util.pool.StateVerifier;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * A class that manages a load by adding and removing callbacks for for the load and notifying\n * callbacks when the load completes.\n */\nclass EngineJob<R> implements DecodeJob.Callback<R>, Poolable {\n  private static final EngineResourceFactory DEFAULT_FACTORY = new EngineResourceFactory();\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  final ResourceCallbacksAndExecutors cbs = new ResourceCallbacksAndExecutors();\n\n  private final StateVerifier stateVerifier = StateVerifier.newInstance();\n  private final ResourceListener resourceListener;\n  private final Pools.Pool<EngineJob<?>> pool;\n  private final EngineResourceFactory engineResourceFactory;\n  private final EngineJobListener engineJobListener;\n  private final GlideExecutor diskCacheExecutor;\n  private final GlideExecutor sourceExecutor;\n  private final GlideExecutor sourceUnlimitedExecutor;\n  private final GlideExecutor animationExecutor;\n  private final AtomicInteger pendingCallbacks = new AtomicInteger();\n\n  private Key key;\n  private boolean isCacheable;\n  private boolean useUnlimitedSourceGeneratorPool;\n  private boolean useAnimationPool;\n  private boolean onlyRetrieveFromCache;\n  private Resource<?> resource;\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  DataSource dataSource;\n\n  private boolean hasResource;\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  GlideException exception;\n\n  private boolean hasLoadFailed;\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  EngineResource<?> engineResource;\n\n  private DecodeJob<R> decodeJob;\n\n  // Checked primarily on the main thread, but also on other threads in reschedule.\n  private volatile boolean isCancelled;\n  private boolean isLoadedFromAlternateCacheKey;\n\n  EngineJob(\n      GlideExecutor diskCacheExecutor,\n      GlideExecutor sourceExecutor,\n      GlideExecutor sourceUnlimitedExecutor,\n      GlideExecutor animationExecutor,\n      EngineJobListener engineJobListener,\n      ResourceListener resourceListener,\n      Pools.Pool<EngineJob<?>> pool) {\n    this(\n        diskCacheExecutor,\n        sourceExecutor,\n        sourceUnlimitedExecutor,\n        animationExecutor,\n        engineJobListener,\n        resourceListener,\n        pool,\n        DEFAULT_FACTORY);\n  }\n\n  @VisibleForTesting\n  EngineJob(\n      GlideExecutor diskCacheExecutor,\n      GlideExecutor sourceExecutor,\n      GlideExecutor sourceUnlimitedExecutor,\n      GlideExecutor animationExecutor,\n      EngineJobListener engineJobListener,\n      ResourceListener resourceListener,\n      Pools.Pool<EngineJob<?>> pool,\n      EngineResourceFactory engineResourceFactory) {\n    this.diskCacheExecutor = diskCacheExecutor;\n    this.sourceExecutor = sourceExecutor;\n    this.sourceUnlimitedExecutor = sourceUnlimitedExecutor;\n    this.animationExecutor = animationExecutor;\n    this.engineJobListener = engineJobListener;\n    this.resourceListener = resourceListener;\n    this.pool = pool;\n    this.engineResourceFactory = engineResourceFactory;\n  }\n\n  @VisibleForTesting\n  synchronized EngineJob<R> init(\n      Key key,\n      boolean isCacheable,\n      boolean useUnlimitedSourceGeneratorPool,\n      boolean useAnimationPool,\n      boolean onlyRetrieveFromCache) {\n    this.key = key;\n    this.isCacheable = isCacheable;\n    this.useUnlimitedSourceGeneratorPool = useUnlimitedSourceGeneratorPool;\n    this.useAnimationPool = useAnimationPool;\n    this.onlyRetrieveFromCache = onlyRetrieveFromCache;\n    return this;\n  }\n\n  public synchronized void start(DecodeJob<R> decodeJob) {\n    this.decodeJob = decodeJob;\n    GlideExecutor executor =\n        decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();\n    executor.execute(decodeJob);\n  }\n\n  synchronized void addCallback(final ResourceCallback cb, Executor callbackExecutor) {\n    stateVerifier.throwIfRecycled();\n    cbs.add(cb, callbackExecutor);\n    if (hasResource) {\n      // Acquire early so that the resource isn't recycled while the Runnable below is still sitting\n      // in the executors queue.\n      incrementPendingCallbacks(1);\n      callbackExecutor.execute(new CallResourceReady(cb));\n    } else if (hasLoadFailed) {\n      incrementPendingCallbacks(1);\n      callbackExecutor.execute(new CallLoadFailed(cb));\n    } else {\n      Preconditions.checkArgument(!isCancelled, \"Cannot add callbacks to a cancelled EngineJob\");\n    }\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  @GuardedBy(\"this\")\n  void callCallbackOnResourceReady(ResourceCallback cb) {\n    try {\n      // This is overly broad, some Glide code is actually called here, but it's much\n      // simpler to encapsulate here than to do so at the actual call point in the\n      // Request implementation.\n      cb.onResourceReady(engineResource, dataSource, isLoadedFromAlternateCacheKey);\n    } catch (Throwable t) {\n      throw new CallbackException(t);\n    }\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  @GuardedBy(\"this\")\n  void callCallbackOnLoadFailed(ResourceCallback cb) {\n    // This is overly broad, some Glide code is actually called here, but it's much\n    // simpler to encapsulate here than to do so at the actual call point in the Request\n    // implementation.\n    try {\n      cb.onLoadFailed(exception);\n    } catch (Throwable t) {\n      throw new CallbackException(t);\n    }\n  }\n\n  synchronized void removeCallback(ResourceCallback cb) {\n    stateVerifier.throwIfRecycled();\n    cbs.remove(cb);\n    if (cbs.isEmpty()) {\n      cancel();\n      boolean isFinishedRunning = hasResource || hasLoadFailed;\n      if (isFinishedRunning && pendingCallbacks.get() == 0) {\n        release();\n      }\n    }\n  }\n\n  boolean onlyRetrieveFromCache() {\n    return onlyRetrieveFromCache;\n  }\n\n  private GlideExecutor getActiveSourceExecutor() {\n    return useUnlimitedSourceGeneratorPool\n        ? sourceUnlimitedExecutor\n        : (useAnimationPool ? animationExecutor : sourceExecutor);\n  }\n\n  // Exposed for testing.\n  void cancel() {\n    if (isDone()) {\n      return;\n    }\n\n    isCancelled = true;\n    decodeJob.cancel();\n    engineJobListener.onEngineJobCancelled(this, key);\n  }\n\n  // Exposed for testing.\n  synchronized boolean isCancelled() {\n    return isCancelled;\n  }\n\n  private boolean isDone() {\n    return hasLoadFailed || hasResource || isCancelled;\n  }\n\n  // We have to post Runnables in a loop. Typically there will be very few callbacks. AccessorMethod\n  // seems to be a false positive\n  @SuppressWarnings({\n    \"WeakerAccess\",\n    \"PMD.AvoidInstantiatingObjectsInLoops\",\n    \"PMD.AccessorMethodGeneration\"\n  })\n  @Synthetic\n  void notifyCallbacksOfResult() {\n    ResourceCallbacksAndExecutors copy;\n    Key localKey;\n    EngineResource<?> localResource;\n    synchronized (this) {\n      stateVerifier.throwIfRecycled();\n      if (isCancelled) {\n        // TODO: Seems like we might as well put this in the memory cache instead of just recycling\n        // it since we've gotten this far...\n        resource.recycle();\n        release();\n        return;\n      } else if (cbs.isEmpty()) {\n        throw new IllegalStateException(\"Received a resource without any callbacks to notify\");\n      } else if (hasResource) {\n        throw new IllegalStateException(\"Already have resource\");\n      }\n      engineResource = engineResourceFactory.build(resource, isCacheable, key, resourceListener);\n      // Hold on to resource for duration of our callbacks below so we don't recycle it in the\n      // middle of notifying if it synchronously released by one of the callbacks. Acquire it under\n      // a lock here so that any newly added callback that executes before the next locked section\n      // below can't recycle the resource before we call the callbacks.\n      hasResource = true;\n      copy = cbs.copy();\n      incrementPendingCallbacks(copy.size() + 1);\n\n      localKey = key;\n      localResource = engineResource;\n    }\n\n    engineJobListener.onEngineJobComplete(this, localKey, localResource);\n\n    for (final ResourceCallbackAndExecutor entry : copy) {\n      entry.executor.execute(new CallResourceReady(entry.cb));\n    }\n    decrementPendingCallbacks();\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  synchronized void incrementPendingCallbacks(int count) {\n    Preconditions.checkArgument(isDone(), \"Not yet complete!\");\n    if (pendingCallbacks.getAndAdd(count) == 0 && engineResource != null) {\n      engineResource.acquire();\n    }\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  void decrementPendingCallbacks() {\n    EngineResource<?> toRelease = null;\n    synchronized (this) {\n      stateVerifier.throwIfRecycled();\n      Preconditions.checkArgument(isDone(), \"Not yet complete!\");\n      int decremented = pendingCallbacks.decrementAndGet();\n      Preconditions.checkArgument(decremented >= 0, \"Can't decrement below 0\");\n      if (decremented == 0) {\n        toRelease = engineResource;\n\n        release();\n      }\n    }\n\n    if (toRelease != null) {\n      toRelease.release();\n    }\n  }\n\n  private synchronized void release() {\n    if (key == null) {\n      throw new IllegalArgumentException();\n    }\n    cbs.clear();\n    key = null;\n    engineResource = null;\n    resource = null;\n    hasLoadFailed = false;\n    isCancelled = false;\n    hasResource = false;\n    isLoadedFromAlternateCacheKey = false;\n    decodeJob.release(/* isRemovedFromQueue= */ false);\n    decodeJob = null;\n    exception = null;\n    dataSource = null;\n    pool.release(this);\n  }\n\n  @Override\n  public void onResourceReady(\n      Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {\n    synchronized (this) {\n      this.resource = resource;\n      this.dataSource = dataSource;\n      this.isLoadedFromAlternateCacheKey = isLoadedFromAlternateCacheKey;\n    }\n    notifyCallbacksOfResult();\n  }\n\n  @Override\n  public void onLoadFailed(GlideException e) {\n    synchronized (this) {\n      this.exception = e;\n    }\n    notifyCallbacksOfException();\n  }\n\n  @Override\n  public void reschedule(DecodeJob<?> job) {\n    // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself\n    // up.\n    getActiveSourceExecutor().execute(job);\n  }\n\n  // We have to post Runnables in a loop. Typically there will be very few callbacks. Acessor method\n  // warning seems to be false positive.\n  @SuppressWarnings({\n    \"WeakerAccess\",\n    \"PMD.AvoidInstantiatingObjectsInLoops\",\n    \"PMD.AccessorMethodGeneration\"\n  })\n  @Synthetic\n  void notifyCallbacksOfException() {\n    ResourceCallbacksAndExecutors copy;\n    Key localKey;\n    synchronized (this) {\n      stateVerifier.throwIfRecycled();\n      if (isCancelled) {\n        release();\n        return;\n      } else if (cbs.isEmpty()) {\n        throw new IllegalStateException(\"Received an exception without any callbacks to notify\");\n      } else if (hasLoadFailed) {\n        throw new IllegalStateException(\"Already failed once\");\n      }\n      hasLoadFailed = true;\n\n      localKey = key;\n\n      copy = cbs.copy();\n      // One for each callback below, plus one for ourselves so that we finish if a callback runs on\n      // another thread before we finish scheduling all of them.\n      incrementPendingCallbacks(copy.size() + 1);\n    }\n\n    engineJobListener.onEngineJobComplete(this, localKey, /* resource= */ null);\n\n    for (ResourceCallbackAndExecutor entry : copy) {\n      entry.executor.execute(new CallLoadFailed(entry.cb));\n    }\n    decrementPendingCallbacks();\n  }\n\n  @NonNull\n  @Override\n  public StateVerifier getVerifier() {\n    return stateVerifier;\n  }\n\n  private class CallLoadFailed implements Runnable {\n\n    private final ResourceCallback cb;\n\n    CallLoadFailed(ResourceCallback cb) {\n      this.cb = cb;\n    }\n\n    @Override\n    public void run() {\n      // Make sure we always acquire the request lock, then the EngineJob lock to avoid deadlock\n      // (b/136032534).\n      synchronized (cb.getLock()) {\n        synchronized (EngineJob.this) {\n          if (cbs.contains(cb)) {\n            callCallbackOnLoadFailed(cb);\n          }\n\n          decrementPendingCallbacks();\n        }\n      }\n    }\n  }\n\n  private class CallResourceReady implements Runnable {\n\n    private final ResourceCallback cb;\n\n    CallResourceReady(ResourceCallback cb) {\n      this.cb = cb;\n    }\n\n    @Override\n    public void run() {\n      // Make sure we always acquire the request lock, then the EngineJob lock to avoid deadlock\n      // (b/136032534).\n      synchronized (cb.getLock()) {\n        synchronized (EngineJob.this) {\n          if (cbs.contains(cb)) {\n            // Acquire for this particular callback.\n            engineResource.acquire();\n            callCallbackOnResourceReady(cb);\n            removeCallback(cb);\n          }\n          decrementPendingCallbacks();\n        }\n      }\n    }\n  }\n\n  static final class ResourceCallbacksAndExecutors\n      implements Iterable<ResourceCallbackAndExecutor> {\n    private final List<ResourceCallbackAndExecutor> callbacksAndExecutors;\n\n    ResourceCallbacksAndExecutors() {\n      this(new ArrayList<ResourceCallbackAndExecutor>(2));\n    }\n\n    ResourceCallbacksAndExecutors(List<ResourceCallbackAndExecutor> callbacksAndExecutors) {\n      this.callbacksAndExecutors = callbacksAndExecutors;\n    }\n\n    void add(ResourceCallback cb, Executor executor) {\n      callbacksAndExecutors.add(new ResourceCallbackAndExecutor(cb, executor));\n    }\n\n    void remove(ResourceCallback cb) {\n      callbacksAndExecutors.remove(defaultCallbackAndExecutor(cb));\n    }\n\n    boolean contains(ResourceCallback cb) {\n      return callbacksAndExecutors.contains(defaultCallbackAndExecutor(cb));\n    }\n\n    boolean isEmpty() {\n      return callbacksAndExecutors.isEmpty();\n    }\n\n    int size() {\n      return callbacksAndExecutors.size();\n    }\n\n    void clear() {\n      callbacksAndExecutors.clear();\n    }\n\n    ResourceCallbacksAndExecutors copy() {\n      return new ResourceCallbacksAndExecutors(new ArrayList<>(callbacksAndExecutors));\n    }\n\n    private static ResourceCallbackAndExecutor defaultCallbackAndExecutor(ResourceCallback cb) {\n      return new ResourceCallbackAndExecutor(cb, Executors.directExecutor());\n    }\n\n    @NonNull\n    @Override\n    public Iterator<ResourceCallbackAndExecutor> iterator() {\n      return callbacksAndExecutors.iterator();\n    }\n  }\n\n  static final class ResourceCallbackAndExecutor {\n    final ResourceCallback cb;\n    final Executor executor;\n\n    ResourceCallbackAndExecutor(ResourceCallback cb, Executor executor) {\n      this.cb = cb;\n      this.executor = executor;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof ResourceCallbackAndExecutor) {\n        ResourceCallbackAndExecutor other = (ResourceCallbackAndExecutor) o;\n        return cb.equals(other.cb);\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      return cb.hashCode();\n    }\n  }\n\n  @VisibleForTesting\n  static class EngineResourceFactory {\n    public <R> EngineResource<R> build(\n        Resource<R> resource, boolean isMemoryCacheable, Key key, ResourceListener listener) {\n      return new EngineResource<>(\n          resource, isMemoryCacheable, /* isRecyclable= */ true, key, listener);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/EngineJobListener.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport com.bumptech.glide.load.Key;\n\ninterface EngineJobListener {\n\n  void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource);\n\n  void onEngineJobCancelled(EngineJob<?> engineJob, Key key);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/EngineKey.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.util.Preconditions;\nimport java.security.MessageDigest;\nimport java.util.Map;\n\n/** An in memory only cache key used to multiplex loads. */\nclass EngineKey implements Key {\n  private final Object model;\n  private final int width;\n  private final int height;\n  private final Class<?> resourceClass;\n  private final Class<?> transcodeClass;\n  private final Key signature;\n  private final Map<Class<?>, Transformation<?>> transformations;\n  private final Options options;\n  private int hashCode;\n\n  EngineKey(\n      Object model,\n      Key signature,\n      int width,\n      int height,\n      Map<Class<?>, Transformation<?>> transformations,\n      Class<?> resourceClass,\n      Class<?> transcodeClass,\n      Options options) {\n    this.model = Preconditions.checkNotNull(model);\n    this.signature = Preconditions.checkNotNull(signature, \"Signature must not be null\");\n    this.width = width;\n    this.height = height;\n    this.transformations = Preconditions.checkNotNull(transformations);\n    this.resourceClass =\n        Preconditions.checkNotNull(resourceClass, \"Resource class must not be null\");\n    this.transcodeClass =\n        Preconditions.checkNotNull(transcodeClass, \"Transcode class must not be null\");\n    this.options = Preconditions.checkNotNull(options);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof EngineKey) {\n      EngineKey other = (EngineKey) o;\n      return model.equals(other.model)\n          && signature.equals(other.signature)\n          && height == other.height\n          && width == other.width\n          && transformations.equals(other.transformations)\n          && resourceClass.equals(other.resourceClass)\n          && transcodeClass.equals(other.transcodeClass)\n          && options.equals(other.options);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    if (hashCode == 0) {\n      hashCode = model.hashCode();\n      hashCode = 31 * hashCode + signature.hashCode();\n      hashCode = 31 * hashCode + width;\n      hashCode = 31 * hashCode + height;\n      hashCode = 31 * hashCode + transformations.hashCode();\n      hashCode = 31 * hashCode + resourceClass.hashCode();\n      hashCode = 31 * hashCode + transcodeClass.hashCode();\n      hashCode = 31 * hashCode + options.hashCode();\n    }\n    return hashCode;\n  }\n\n  @Override\n  public String toString() {\n    return \"EngineKey{\"\n        + \"model=\"\n        + model\n        + \", width=\"\n        + width\n        + \", height=\"\n        + height\n        + \", resourceClass=\"\n        + resourceClass\n        + \", transcodeClass=\"\n        + transcodeClass\n        + \", signature=\"\n        + signature\n        + \", hashCode=\"\n        + hashCode\n        + \", transformations=\"\n        + transformations\n        + \", options=\"\n        + options\n        + '}';\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    throw new UnsupportedOperationException();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/EngineKeyFactory.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport java.util.Map;\n\nclass EngineKeyFactory {\n\n  @SuppressWarnings(\"rawtypes\")\n  EngineKey buildKey(\n      Object model,\n      Key signature,\n      int width,\n      int height,\n      Map<Class<?>, Transformation<?>> transformations,\n      Class<?> resourceClass,\n      Class<?> transcodeClass,\n      Options options) {\n    return new EngineKey(\n        model, signature, width, height, transformations, resourceClass, transcodeClass, options);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/EngineResource.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * A wrapper resource that allows reference counting a wrapped {@link\n * com.bumptech.glide.load.engine.Resource} interface.\n *\n * @param <Z> The type of data returned by the wrapped {@link Resource}.\n */\nclass EngineResource<Z> implements Resource<Z> {\n  private final boolean isMemoryCacheable;\n  private final boolean isRecyclable;\n  private final Resource<Z> resource;\n  private final ResourceListener listener;\n  private final Key key;\n\n  private int acquired;\n  private boolean isRecycled;\n\n  interface ResourceListener {\n    void onResourceReleased(Key key, EngineResource<?> resource);\n  }\n\n  EngineResource(\n      Resource<Z> toWrap,\n      boolean isMemoryCacheable,\n      boolean isRecyclable,\n      Key key,\n      ResourceListener listener) {\n    resource = Preconditions.checkNotNull(toWrap);\n    this.isMemoryCacheable = isMemoryCacheable;\n    this.isRecyclable = isRecyclable;\n    this.key = key;\n    this.listener = Preconditions.checkNotNull(listener);\n  }\n\n  Resource<Z> getResource() {\n    return resource;\n  }\n\n  boolean isMemoryCacheable() {\n    return isMemoryCacheable;\n  }\n\n  @NonNull\n  @Override\n  public Class<Z> getResourceClass() {\n    return resource.getResourceClass();\n  }\n\n  @NonNull\n  @Override\n  public Z get() {\n    return resource.get();\n  }\n\n  @Override\n  public int getSize() {\n    return resource.getSize();\n  }\n\n  @Override\n  public synchronized void recycle() {\n    if (acquired > 0) {\n      throw new IllegalStateException(\"Cannot recycle a resource while it is still acquired\");\n    }\n    if (isRecycled) {\n      throw new IllegalStateException(\"Cannot recycle a resource that has already been recycled\");\n    }\n    isRecycled = true;\n    if (isRecyclable) {\n      resource.recycle();\n    }\n  }\n\n  /**\n   * Increments the number of consumers using the wrapped resource. Must be called on the main\n   * thread.\n   *\n   * <p>This must be called with a number corresponding to the number of new consumers each time new\n   * consumers begin using the wrapped resource. It is always safer to call acquire more often than\n   * necessary. Generally external users should never call this method, the framework will take care\n   * of this for you.\n   */\n  synchronized void acquire() {\n    if (isRecycled) {\n      throw new IllegalStateException(\"Cannot acquire a recycled resource\");\n    }\n    ++acquired;\n  }\n\n  /**\n   * Decrements the number of consumers using the wrapped resource. Must be called on the main\n   * thread.\n   *\n   * <p>This must only be called when a consumer that called the {@link #acquire()} method is now\n   * done with the resource. Generally external users should never call this method, the framework\n   * will take care of this for you.\n   */\n  // listener is effectively final.\n  @SuppressWarnings(\"SynchronizeOnNonFinalField\")\n  void release() {\n    boolean release = false;\n    synchronized (this) {\n      if (acquired <= 0) {\n        throw new IllegalStateException(\"Cannot release a recycled or not yet acquired resource\");\n      }\n      if (--acquired == 0) {\n        release = true;\n      }\n    }\n    if (release) {\n      listener.onResourceReleased(key, this);\n    }\n  }\n\n  @Override\n  public synchronized String toString() {\n    return \"EngineResource{\"\n        + \"isMemoryCacheable=\"\n        + isMemoryCacheable\n        + \", listener=\"\n        + listener\n        + \", key=\"\n        + key\n        + \", acquired=\"\n        + acquired\n        + \", isRecycled=\"\n        + isRecycled\n        + \", resource=\"\n        + resource\n        + '}';\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/GlideException.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.io.PrintWriter;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/** An exception with zero or more causes indicating why a load in Glide failed. */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic final class GlideException extends Exception {\n  private static final long serialVersionUID = 1L;\n\n  private static final StackTraceElement[] EMPTY_ELEMENTS = new StackTraceElement[0];\n\n  private final List<Throwable> causes;\n  private Key key;\n  private DataSource dataSource;\n  private Class<?> dataClass;\n  private String detailMessage;\n  @Nullable private Exception exception;\n\n  public GlideException(String message) {\n    this(message, Collections.<Throwable>emptyList());\n  }\n\n  public GlideException(String detailMessage, Throwable cause) {\n    this(detailMessage, Collections.singletonList(cause));\n  }\n\n  public GlideException(String detailMessage, List<Throwable> causes) {\n    this.detailMessage = detailMessage;\n    setStackTrace(EMPTY_ELEMENTS);\n    this.causes = causes;\n  }\n\n  void setLoggingDetails(Key key, DataSource dataSource) {\n    setLoggingDetails(key, dataSource, null);\n  }\n\n  void setLoggingDetails(Key key, DataSource dataSource, Class<?> dataClass) {\n    this.key = key;\n    this.dataSource = dataSource;\n    this.dataClass = dataClass;\n  }\n\n  /**\n   * Sets a stack trace that includes where the request originated.\n   *\n   * <p>This is an experimental API that may be removed in the future.\n   */\n  public void setOrigin(@Nullable Exception exception) {\n    this.exception = exception;\n  }\n\n  /**\n   * Returns an {@link Exception} with a stack trace that includes where the request originated (if\n   * previously set via {@link #setOrigin(Exception)})\n   *\n   * <p>This is an experimental API that may be removed in the future.\n   */\n  @Nullable\n  public Exception getOrigin() {\n    return exception;\n  }\n\n  // No need to synchronize when doing nothing whatsoever.\n  @SuppressWarnings(\"UnsynchronizedOverridesSynchronized\")\n  @Override\n  public Throwable fillInStackTrace() {\n    // Avoid an expensive allocation by doing nothing here. Causes should contain all relevant\n    // stack traces.\n    return this;\n  }\n\n  /**\n   * Returns a list of causes that are immediate children of this exception.\n   *\n   * <p>Causes may or may not be {@link GlideException GlideExceptions}. Causes may also not be root\n   * causes, and in turn my have been caused by other failures.\n   *\n   * @see #getRootCauses()\n   */\n  public List<Throwable> getCauses() {\n    return causes;\n  }\n\n  /**\n   * Returns the list of root causes that are the leaf nodes of all children of this exception.\n   *\n   * <p>Use this method to do things like look for http exceptions that indicate the load may have\n   * failed due to an error that can be retried. Keep in mind that because Glide may attempt to load\n   * a given model using multiple different pathways, there may be multiple related or unrelated\n   * reasons for a load to fail.\n   */\n  public List<Throwable> getRootCauses() {\n    List<Throwable> rootCauses = new ArrayList<>();\n    addRootCauses(this, rootCauses);\n    return rootCauses;\n  }\n\n  /**\n   * Logs all root causes using the given tag.\n   *\n   * <p>Each root cause is logged separately to avoid throttling. {@link #printStackTrace()} will\n   * provide a more succinct overview of why the exception occurred, although it does not include\n   * complete stack traces.\n   */\n  public void logRootCauses(String tag) {\n    List<Throwable> causes = getRootCauses();\n    for (int i = 0, size = causes.size(); i < size; i++) {\n      Log.i(tag, \"Root cause (\" + (i + 1) + \" of \" + size + \")\", causes.get(i));\n    }\n  }\n\n  private void addRootCauses(Throwable throwable, List<Throwable> rootCauses) {\n    if (throwable instanceof GlideException) {\n      GlideException glideException = (GlideException) throwable;\n      for (Throwable t : glideException.getCauses()) {\n        addRootCauses(t, rootCauses);\n      }\n    } else if (throwable != null) {\n      rootCauses.add(throwable);\n    }\n  }\n\n  @Override\n  public void printStackTrace() {\n    printStackTrace(System.err);\n  }\n\n  @Override\n  public void printStackTrace(PrintStream err) {\n    printStackTrace((Appendable) err);\n  }\n\n  @Override\n  public void printStackTrace(PrintWriter err) {\n    printStackTrace((Appendable) err);\n  }\n\n  private void printStackTrace(Appendable appendable) {\n    appendExceptionMessage(this, appendable);\n    appendCauses(getCauses(), new IndentedAppendable(appendable));\n  }\n\n  // PMD doesn't seem to notice that we're allocating the builder with the suggested size.\n  @SuppressWarnings(\"PMD.InsufficientStringBufferDeclaration\")\n  @Override\n  public String getMessage() {\n    StringBuilder result =\n        new StringBuilder(71)\n            .append(detailMessage)\n            .append(dataClass != null ? \", \" + dataClass : \"\")\n            .append(dataSource != null ? \", \" + dataSource : \"\")\n            .append(key != null ? \", \" + key : \"\");\n\n    List<Throwable> rootCauses = getRootCauses();\n    if (rootCauses.isEmpty()) {\n      return result.toString();\n    } else if (rootCauses.size() == 1) {\n      result.append(\"\\nThere was 1 root cause:\");\n    } else {\n      result.append(\"\\nThere were \").append(rootCauses.size()).append(\" root causes:\");\n    }\n    for (Throwable cause : rootCauses) {\n      result\n          .append('\\n')\n          .append(cause.getClass().getName())\n          .append('(')\n          .append(cause.getMessage())\n          .append(')');\n    }\n    result.append(\"\\n call GlideException#logRootCauses(String) for more detail\");\n    return result.toString();\n  }\n\n  // Appendable throws, PrintWriter, PrintStream, and IndentedAppendable do not, so this should\n  // never happen.\n  @SuppressWarnings(\"PMD.PreserveStackTrace\")\n  private static void appendExceptionMessage(Throwable t, Appendable appendable) {\n    try {\n      appendable.append(t.getClass().toString()).append(\": \").append(t.getMessage()).append('\\n');\n    } catch (IOException e1) {\n      throw new RuntimeException(t);\n    }\n  }\n\n  // Appendable throws, PrintWriter, PrintStream, and IndentedAppendable do not, so this should\n  // never happen.\n  @SuppressWarnings(\"PMD.PreserveStackTrace\")\n  private static void appendCauses(List<Throwable> causes, Appendable appendable) {\n    try {\n      appendCausesWrapped(causes, appendable);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @SuppressWarnings(\"ThrowableResultOfMethodCallIgnored\")\n  private static void appendCausesWrapped(List<Throwable> causes, Appendable appendable)\n      throws IOException {\n    int size = causes.size();\n    for (int i = 0; i < size; i++) {\n      appendable\n          .append(\"Cause (\")\n          .append(String.valueOf(i + 1))\n          .append(\" of \")\n          .append(String.valueOf(size))\n          .append(\"): \");\n\n      Throwable cause = causes.get(i);\n      if (cause instanceof GlideException) {\n        GlideException glideCause = (GlideException) cause;\n        glideCause.printStackTrace(appendable);\n      } else {\n        appendExceptionMessage(cause, appendable);\n      }\n    }\n  }\n\n  private static final class IndentedAppendable implements Appendable {\n    private static final String EMPTY_SEQUENCE = \"\";\n    private static final String INDENT = \"  \";\n    private final Appendable appendable;\n    private boolean printedNewLine = true;\n\n    IndentedAppendable(Appendable appendable) {\n      this.appendable = appendable;\n    }\n\n    @Override\n    public Appendable append(char c) throws IOException {\n      if (printedNewLine) {\n        printedNewLine = false;\n        appendable.append(INDENT);\n      }\n      printedNewLine = c == '\\n';\n      appendable.append(c);\n      return this;\n    }\n\n    @Override\n    public Appendable append(@Nullable CharSequence charSequence) throws IOException {\n      charSequence = safeSequence(charSequence);\n      return append(charSequence, 0, charSequence.length());\n    }\n\n    @Override\n    public Appendable append(@Nullable CharSequence charSequence, int start, int end)\n        throws IOException {\n      charSequence = safeSequence(charSequence);\n      if (printedNewLine) {\n        printedNewLine = false;\n        appendable.append(INDENT);\n      }\n      printedNewLine = charSequence.length() > 0 && charSequence.charAt(end - 1) == '\\n';\n      appendable.append(charSequence, start, end);\n      return this;\n    }\n\n    @NonNull\n    private CharSequence safeSequence(@Nullable CharSequence sequence) {\n      if (sequence == null) {\n        return EMPTY_SEQUENCE;\n      } else {\n        return sequence;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/Initializable.java",
    "content": "package com.bumptech.glide.load.engine;\n\n/**\n * A callback allowing a resource to do some optimization on a background thread before being\n * returned to the ui.\n */\npublic interface Initializable {\n\n  /** Called on a background thread so the {@link Resource} can do some eager initialization. */\n  void initialize();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/Jobs.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.Key;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nfinal class Jobs {\n  private final Map<Key, EngineJob<?>> jobs = new HashMap<>();\n  private final Map<Key, EngineJob<?>> onlyCacheJobs = new HashMap<>();\n\n  @VisibleForTesting\n  Map<Key, EngineJob<?>> getAll() {\n    return Collections.unmodifiableMap(jobs);\n  }\n\n  EngineJob<?> get(Key key, boolean onlyRetrieveFromCache) {\n    return getJobMap(onlyRetrieveFromCache).get(key);\n  }\n\n  void put(Key key, EngineJob<?> job) {\n    getJobMap(job.onlyRetrieveFromCache()).put(key, job);\n  }\n\n  void removeIfCurrent(Key key, EngineJob<?> expected) {\n    Map<Key, EngineJob<?>> jobMap = getJobMap(expected.onlyRetrieveFromCache());\n    if (expected.equals(jobMap.get(key))) {\n      jobMap.remove(key);\n    }\n  }\n\n  private Map<Key, EngineJob<?>> getJobMap(boolean onlyRetrieveFromCache) {\n    return onlyRetrieveFromCache ? onlyCacheJobs : jobs;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/LoadPath.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport androidx.core.util.Pools.Pool;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataRewinder;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * For a given {@link com.bumptech.glide.load.data.DataFetcher} for a given data class, attempts to\n * fetch the data and then run it through one or more {@link\n * com.bumptech.glide.load.engine.DecodePath}s.\n *\n * @param <Data> The type of data that will be fetched.\n * @param <ResourceType> The type of intermediate resource that will be decoded within one of the\n *     {@link com.bumptech.glide.load.engine.DecodePath}s.\n * @param <Transcode> The type of resource that will be returned as the result if the load and one\n *     of the decode paths succeeds.\n */\npublic class LoadPath<Data, ResourceType, Transcode> {\n  private final Class<Data> dataClass;\n  private final Pool<List<Throwable>> listPool;\n  private final List<? extends DecodePath<Data, ResourceType, Transcode>> decodePaths;\n  private final String failureMessage;\n\n  public LoadPath(\n      Class<Data> dataClass,\n      Class<ResourceType> resourceClass,\n      Class<Transcode> transcodeClass,\n      List<DecodePath<Data, ResourceType, Transcode>> decodePaths,\n      Pool<List<Throwable>> listPool) {\n    this.dataClass = dataClass;\n    this.listPool = listPool;\n    this.decodePaths = Preconditions.checkNotEmpty(decodePaths);\n    failureMessage =\n        \"Failed LoadPath{\"\n            + dataClass.getSimpleName()\n            + \"->\"\n            + resourceClass.getSimpleName()\n            + \"->\"\n            + transcodeClass.getSimpleName()\n            + \"}\";\n  }\n\n  public Resource<Transcode> load(\n      DataRewinder<Data> rewinder,\n      @NonNull Options options,\n      int width,\n      int height,\n      DecodePath.DecodeCallback<ResourceType> decodeCallback)\n      throws GlideException {\n    List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());\n    try {\n      return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);\n    } finally {\n      listPool.release(throwables);\n    }\n  }\n\n  private Resource<Transcode> loadWithExceptionList(\n      DataRewinder<Data> rewinder,\n      @NonNull Options options,\n      int width,\n      int height,\n      DecodePath.DecodeCallback<ResourceType> decodeCallback,\n      List<Throwable> exceptions)\n      throws GlideException {\n    Resource<Transcode> result = null;\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0, size = decodePaths.size(); i < size; i++) {\n      DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);\n      try {\n        result = path.decode(rewinder, width, height, options, decodeCallback);\n      } catch (GlideException e) {\n        exceptions.add(e);\n      }\n      if (result != null) {\n        break;\n      }\n    }\n\n    if (result == null) {\n      throw new GlideException(failureMessage, new ArrayList<>(exceptions));\n    }\n\n    return result;\n  }\n\n  public Class<Data> getDataClass() {\n    return dataClass;\n  }\n\n  @Override\n  public String toString() {\n    return \"LoadPath{\" + \"decodePaths=\" + Arrays.toString(decodePaths.toArray()) + '}';\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/LockedResource.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport androidx.core.util.Pools;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.pool.FactoryPools;\nimport com.bumptech.glide.util.pool.StateVerifier;\n\n/**\n * A resource that defers any calls to {@link Resource#recycle()} until after {@link #unlock()} is\n * called.\n *\n * <p>If the resource was recycled prior to {@link #unlock()}, then {@link #unlock()} will also\n * recycle the resource.\n */\nfinal class LockedResource<Z> implements Resource<Z>, FactoryPools.Poolable {\n  private static final Pools.Pool<LockedResource<?>> POOL =\n      FactoryPools.threadSafe(\n          20,\n          new FactoryPools.Factory<LockedResource<?>>() {\n            @Override\n            public LockedResource<?> create() {\n              return new LockedResource<Object>();\n            }\n          });\n  private final StateVerifier stateVerifier = StateVerifier.newInstance();\n  private Resource<Z> toWrap;\n  private boolean isLocked;\n  private boolean isRecycled;\n\n  @SuppressWarnings(\"unchecked\")\n  @NonNull\n  static <Z> LockedResource<Z> obtain(Resource<Z> resource) {\n    LockedResource<Z> result = Preconditions.checkNotNull((LockedResource<Z>) POOL.acquire());\n    result.init(resource);\n    return result;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  LockedResource() {}\n\n  private void init(Resource<Z> toWrap) {\n    isRecycled = false;\n    isLocked = true;\n    this.toWrap = toWrap;\n  }\n\n  private void release() {\n    toWrap = null;\n    POOL.release(this);\n  }\n\n  synchronized void unlock() {\n    stateVerifier.throwIfRecycled();\n\n    if (!isLocked) {\n      throw new IllegalStateException(\"Already unlocked\");\n    }\n    this.isLocked = false;\n    if (isRecycled) {\n      recycle();\n    }\n  }\n\n  @NonNull\n  @Override\n  public Class<Z> getResourceClass() {\n    return toWrap.getResourceClass();\n  }\n\n  @NonNull\n  @Override\n  public Z get() {\n    return toWrap.get();\n  }\n\n  @Override\n  public int getSize() {\n    return toWrap.getSize();\n  }\n\n  @Override\n  public synchronized void recycle() {\n    stateVerifier.throwIfRecycled();\n\n    this.isRecycled = true;\n    if (!isLocked) {\n      toWrap.recycle();\n      release();\n    }\n  }\n\n  @NonNull\n  @Override\n  public StateVerifier getVerifier() {\n    return stateVerifier;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/Resource.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\n\n/**\n * A resource interface that wraps a particular type so that it can be pooled and reused.\n *\n * @param <Z> The type of resource wrapped by this class.\n */\npublic interface Resource<Z> {\n\n  /** Returns the {@link Class} of the wrapped resource. */\n  @NonNull\n  Class<Z> getResourceClass();\n\n  /**\n   * Returns an instance of the wrapped resource.\n   *\n   * <p>Note - This does not have to be the same instance of the wrapped resource class and in fact\n   * it is often appropriate to return a new instance for each call. For example, {@link\n   * android.graphics.drawable.Drawable Drawable}s should only be used by a single {@link\n   * android.view.View View} at a time so each call to this method for Resources that wrap {@link\n   * android.graphics.drawable.Drawable Drawable}s should always return a new {@link\n   * android.graphics.drawable.Drawable Drawable}.\n   */\n  @NonNull\n  Z get();\n\n  /**\n   * Returns the size in bytes of the wrapped resource to use to determine how much of the memory\n   * cache this resource uses.\n   */\n  int getSize();\n\n  /**\n   * Cleans up and recycles internal resources.\n   *\n   * <p>It is only safe to call this method if there are no current resource consumers and if this\n   * method has not yet been called. Typically this occurs at one of two times:\n   *\n   * <ul>\n   *   <li>During a resource load when the resource is transformed or transcoded before any consumer\n   *       have ever had access to this resource\n   *   <li>After all consumers have released this resource and it has been evicted from the cache\n   * </ul>\n   *\n   * For most users of this class, the only time this method should ever be called is during\n   * transformations or transcoders, the framework will call this method when all consumers have\n   * released this resource and it has been evicted from the cache.\n   */\n  void recycle();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/ResourceCacheGenerator.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoader.LoadData;\nimport com.bumptech.glide.util.pool.GlideTrace;\nimport java.io.File;\nimport java.util.List;\n\n/**\n * Generates {@link com.bumptech.glide.load.data.DataFetcher DataFetchers} from cache files\n * containing downsampled/transformed resource data.\n */\nclass ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {\n\n  private final FetcherReadyCallback cb;\n  private final DecodeHelper<?> helper;\n\n  private int sourceIdIndex;\n  private int resourceClassIndex = -1;\n  private Key sourceKey;\n  private List<ModelLoader<File, ?>> modelLoaders;\n  private int modelLoaderIndex;\n  private volatile LoadData<?> loadData;\n\n  // PMD is wrong here, this File must be an instance variable because it may be used across\n  // multiple calls to startNext.\n  @SuppressWarnings(\"PMD.SingularField\")\n  private File cacheFile;\n\n  private ResourceCacheKey currentKey;\n\n  ResourceCacheGenerator(DecodeHelper<?> helper, FetcherReadyCallback cb) {\n    this.helper = helper;\n    this.cb = cb;\n  }\n\n  // See TODO below.\n  @SuppressWarnings(\"PMD.CollapsibleIfStatements\")\n  @Override\n  public boolean startNext() {\n    GlideTrace.beginSection(\"ResourceCacheGenerator.startNext\");\n    try {\n      List<Key> sourceIds = helper.getCacheKeys();\n      if (sourceIds.isEmpty()) {\n        return false;\n      }\n      List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();\n      if (resourceClasses.isEmpty()) {\n        if (File.class.equals(helper.getTranscodeClass())) {\n          return false;\n        }\n        throw new IllegalStateException(\n            \"Failed to find any load path from \"\n                + helper.getModelClass()\n                + \" to \"\n                + helper.getTranscodeClass());\n      }\n      while (modelLoaders == null || !hasNextModelLoader()) {\n        resourceClassIndex++;\n        if (resourceClassIndex >= resourceClasses.size()) {\n          sourceIdIndex++;\n          if (sourceIdIndex >= sourceIds.size()) {\n            return false;\n          }\n          resourceClassIndex = 0;\n        }\n\n        Key sourceId = sourceIds.get(sourceIdIndex);\n        Class<?> resourceClass = resourceClasses.get(resourceClassIndex);\n        Transformation<?> transformation = helper.getTransformation(resourceClass);\n        // PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,\n        // we only run until the first one succeeds, the loop runs for only a limited\n        // number of iterations on the order of 10-20 in the worst case.\n        currentKey =\n            new ResourceCacheKey( // NOPMD AvoidInstantiatingObjectsInLoops\n                helper.getArrayPool(),\n                sourceId,\n                helper.getSignature(),\n                helper.getWidth(),\n                helper.getHeight(),\n                transformation,\n                resourceClass,\n                helper.getOptions());\n        cacheFile = helper.getDiskCache().get(currentKey);\n        if (cacheFile != null) {\n          sourceKey = sourceId;\n          modelLoaders = helper.getModelLoaders(cacheFile);\n          modelLoaderIndex = 0;\n        }\n      }\n\n      loadData = null;\n      boolean started = false;\n      while (!started && hasNextModelLoader()) {\n        ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);\n        loadData =\n            modelLoader.buildLoadData(\n                cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());\n        if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {\n          started = true;\n          loadData.fetcher.loadData(helper.getPriority(), this);\n        }\n      }\n\n      return started;\n    } finally {\n      GlideTrace.endSection();\n    }\n  }\n\n  private boolean hasNextModelLoader() {\n    return modelLoaderIndex < modelLoaders.size();\n  }\n\n  @Override\n  public void cancel() {\n    LoadData<?> local = loadData;\n    if (local != null) {\n      local.fetcher.cancel();\n    }\n  }\n\n  @Override\n  public void onDataReady(Object data) {\n    cb.onDataFetcherReady(\n        sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey);\n  }\n\n  @Override\n  public void onLoadFailed(@NonNull Exception e) {\n    cb.onDataFetcherFailed(currentKey, e, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/ResourceCacheKey.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.util.LruCache;\nimport com.bumptech.glide.util.Util;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\n\n/** A cache key for downsampled and transformed resource data + any requested signature. */\nfinal class ResourceCacheKey implements Key {\n  private static final LruCache<Class<?>, byte[]> RESOURCE_CLASS_BYTES = new LruCache<>(50);\n  private final ArrayPool arrayPool;\n  private final Key sourceKey;\n  private final Key signature;\n  private final int width;\n  private final int height;\n  private final Class<?> decodedResourceClass;\n  private final Options options;\n  private final Transformation<?> transformation;\n\n  ResourceCacheKey(\n      ArrayPool arrayPool,\n      Key sourceKey,\n      Key signature,\n      int width,\n      int height,\n      Transformation<?> appliedTransformation,\n      Class<?> decodedResourceClass,\n      Options options) {\n    this.arrayPool = arrayPool;\n    this.sourceKey = sourceKey;\n    this.signature = signature;\n    this.width = width;\n    this.height = height;\n    this.transformation = appliedTransformation;\n    this.decodedResourceClass = decodedResourceClass;\n    this.options = options;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof ResourceCacheKey) {\n      ResourceCacheKey other = (ResourceCacheKey) o;\n      return height == other.height\n          && width == other.width\n          && Util.bothNullOrEqual(transformation, other.transformation)\n          && decodedResourceClass.equals(other.decodedResourceClass)\n          && sourceKey.equals(other.sourceKey)\n          && signature.equals(other.signature)\n          && options.equals(other.options);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    int result = sourceKey.hashCode();\n    result = 31 * result + signature.hashCode();\n    result = 31 * result + width;\n    result = 31 * result + height;\n    if (transformation != null) {\n      result = 31 * result + transformation.hashCode();\n    }\n    result = 31 * result + decodedResourceClass.hashCode();\n    result = 31 * result + options.hashCode();\n    return result;\n  }\n\n  // TODO: Include relevant options?\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    byte[] dimensions = arrayPool.getExact(8, byte[].class);\n    ByteBuffer.wrap(dimensions).putInt(width).putInt(height).array();\n    signature.updateDiskCacheKey(messageDigest);\n    sourceKey.updateDiskCacheKey(messageDigest);\n    messageDigest.update(dimensions);\n    if (transformation != null) {\n      transformation.updateDiskCacheKey(messageDigest);\n    }\n    options.updateDiskCacheKey(messageDigest);\n    messageDigest.update(getResourceClassBytes());\n    arrayPool.put(dimensions);\n  }\n\n  private byte[] getResourceClassBytes() {\n    byte[] result = RESOURCE_CLASS_BYTES.get(decodedResourceClass);\n    if (result == null) {\n      result = decodedResourceClass.getName().getBytes(CHARSET);\n      RESOURCE_CLASS_BYTES.put(decodedResourceClass, result);\n    }\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return \"ResourceCacheKey{\"\n        + \"sourceKey=\"\n        + sourceKey\n        + \", signature=\"\n        + signature\n        + \", width=\"\n        + width\n        + \", height=\"\n        + height\n        + \", decodedResourceClass=\"\n        + decodedResourceClass\n        + \", transformation='\"\n        + transformation\n        + '\\''\n        + \", options=\"\n        + options\n        + '}';\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/ResourceRecycler.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport com.bumptech.glide.util.Synthetic;\n\n/** A class that can safely recycle recursive resources. */\nclass ResourceRecycler {\n  private boolean isRecycling;\n  private final Handler handler =\n      new Handler(Looper.getMainLooper(), new ResourceRecyclerCallback());\n\n  synchronized void recycle(Resource<?> resource, boolean forceNextFrame) {\n    if (isRecycling || forceNextFrame) {\n      // If a resource has sub-resources, releasing a sub resource can cause it's parent to be\n      // synchronously evicted which leads to a recycle loop when the parent releases it's children.\n      // Posting breaks this loop.\n      handler.obtainMessage(ResourceRecyclerCallback.RECYCLE_RESOURCE, resource).sendToTarget();\n    } else {\n      isRecycling = true;\n      resource.recycle();\n      isRecycling = false;\n    }\n  }\n\n  private static final class ResourceRecyclerCallback implements Handler.Callback {\n    static final int RECYCLE_RESOURCE = 1;\n\n    @Synthetic\n    ResourceRecyclerCallback() {}\n\n    @Override\n    public boolean handleMessage(Message message) {\n      if (message.what == RECYCLE_RESOURCE) {\n        Resource<?> resource = (Resource<?>) message.obj;\n        resource.recycle();\n        return true;\n      }\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/SourceGenerator.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Encoder;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.DataFetcher.DataCallback;\nimport com.bumptech.glide.load.data.DataRewinder;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoader.LoadData;\nimport com.bumptech.glide.util.LogTime;\nimport com.bumptech.glide.util.Synthetic;\nimport java.io.IOException;\nimport java.util.Collections;\n\n/**\n * Generates {@link com.bumptech.glide.load.data.DataFetcher DataFetchers} from original source data\n * using registered {@link com.bumptech.glide.load.model.ModelLoader ModelLoaders} and the model\n * provided for the load.\n *\n * <p>Depending on the disk cache strategy, source data may first be written to disk and then loaded\n * from the cache file rather than returned directly.\n *\n * <p>This object may be used by multiple threads, but only one at a time. It is not safe to access\n * this object on multiple threads concurrently.\n */\nclass SourceGenerator implements DataFetcherGenerator, DataFetcherGenerator.FetcherReadyCallback {\n  private static final String TAG = \"SourceGenerator\";\n\n  private final DecodeHelper<?> helper;\n  private final FetcherReadyCallback cb;\n\n  private volatile int loadDataListIndex;\n  private volatile DataCacheGenerator sourceCacheGenerator;\n  private volatile Object dataToCache;\n  private volatile ModelLoader.LoadData<?> loadData;\n  private volatile DataCacheKey originalKey;\n\n  SourceGenerator(DecodeHelper<?> helper, FetcherReadyCallback cb) {\n    this.helper = helper;\n    this.cb = cb;\n  }\n\n  // Concurrent access isn't supported.\n  @SuppressWarnings({\"NonAtomicOperationOnVolatileField\", \"NonAtomicVolatileUpdate\"})\n  @Override\n  public boolean startNext() {\n    if (dataToCache != null) {\n      Object data = dataToCache;\n      dataToCache = null;\n      try {\n        boolean isDataInCache = cacheData(data);\n        // If we failed to write the data to cache, the cacheData method will try to decode the\n        // original data directly instead of going through the disk cache. Since cacheData has\n        // already called our callback at this point, there's nothing more to do but return.\n        if (!isDataInCache) {\n          return true;\n        }\n        // If we were able to write the data to cache successfully, we now need to proceed to call\n        // the sourceCacheGenerator below to load the data from cache.\n      } catch (IOException e) {\n        // An IOException means we weren't able to write data to cache or we weren't able to rewind\n        // it after a disk cache write failed. In either case we can just move on and try the next\n        // fetch below.\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Failed to properly rewind or write data to cache\", e);\n        }\n      }\n    }\n\n    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {\n      return true;\n    }\n    sourceCacheGenerator = null;\n\n    loadData = null;\n    boolean started = false;\n    while (!started && hasNextModelLoader()) {\n      loadData = helper.getLoadData().get(loadDataListIndex++);\n      if (loadData != null\n          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())\n              || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {\n        started = true;\n        startNextLoad(loadData);\n      }\n    }\n    return started;\n  }\n\n  private void startNextLoad(final LoadData<?> toStart) {\n    loadData.fetcher.loadData(\n        helper.getPriority(),\n        new DataCallback<Object>() {\n          @Override\n          public void onDataReady(@Nullable Object data) {\n            if (isCurrentRequest(toStart)) {\n              onDataReadyInternal(toStart, data);\n            }\n          }\n\n          @Override\n          public void onLoadFailed(@NonNull Exception e) {\n            if (isCurrentRequest(toStart)) {\n              onLoadFailedInternal(toStart, e);\n            }\n          }\n        });\n  }\n\n  // We want reference equality explicitly to make sure we ignore results from old requests.\n  @SuppressWarnings({\"PMD.CompareObjectsWithEquals\", \"WeakerAccess\"})\n  @Synthetic\n  boolean isCurrentRequest(LoadData<?> requestLoadData) {\n    LoadData<?> currentLoadData = loadData;\n    return currentLoadData != null && currentLoadData == requestLoadData;\n  }\n\n  private boolean hasNextModelLoader() {\n    return loadDataListIndex < helper.getLoadData().size();\n  }\n\n  /**\n   * Returns {@code true} if we were able to cache the data and should try to decode the data\n   * directly from cache and {@code false} if we were unable to cache the data and should make an\n   * attempt to decode from source.\n   */\n  private boolean cacheData(Object dataToCache) throws IOException {\n    long startTime = LogTime.getLogTime();\n    boolean isLoadingFromSourceData = false;\n    try {\n      DataRewinder<Object> rewinder = helper.getRewinder(dataToCache);\n      Object data = rewinder.rewindAndGet();\n      Encoder<Object> encoder = helper.getSourceEncoder(data);\n      DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, data, helper.getOptions());\n      DataCacheKey newOriginalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());\n      DiskCache diskCache = helper.getDiskCache();\n      diskCache.put(newOriginalKey, writer);\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(\n            TAG,\n            \"Finished encoding source to cache\"\n                + \", key: \"\n                + newOriginalKey\n                + \", data: \"\n                + dataToCache\n                + \", encoder: \"\n                + encoder\n                + \", duration: \"\n                + LogTime.getElapsedMillis(startTime));\n      }\n\n      if (diskCache.get(newOriginalKey) != null) {\n        originalKey = newOriginalKey;\n        sourceCacheGenerator =\n            new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);\n        // We were able to write the data to cache.\n        return true;\n      } else {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(\n              TAG,\n              \"Attempt to write: \"\n                  + originalKey\n                  + \", data: \"\n                  + dataToCache\n                  + \" to the disk\"\n                  + \" cache failed, maybe the disk cache is disabled?\"\n                  + \" Trying to decode the data directly...\");\n        }\n\n        isLoadingFromSourceData = true;\n        cb.onDataFetcherReady(\n            loadData.sourceKey,\n            rewinder.rewindAndGet(),\n            loadData.fetcher,\n            loadData.fetcher.getDataSource(),\n            loadData.sourceKey);\n      }\n      // We failed to write the data to cache.\n      return false;\n    } finally {\n      if (!isLoadingFromSourceData) {\n        loadData.fetcher.cleanup();\n      }\n    }\n  }\n\n  @Override\n  public void cancel() {\n    LoadData<?> local = loadData;\n    if (local != null) {\n      local.fetcher.cancel();\n    }\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  void onDataReadyInternal(LoadData<?> loadData, Object data) {\n    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();\n    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {\n      dataToCache = data;\n      // We might be being called back on someone else's thread. Before doing anything, we should\n      // reschedule to get back onto Glide's thread. Then once we're back on Glide's thread, we'll\n      // get called again and we can write the retrieved data to cache.\n      cb.reschedule();\n    } else {\n      cb.onDataFetcherReady(\n          loadData.sourceKey,\n          data,\n          loadData.fetcher,\n          loadData.fetcher.getDataSource(),\n          originalKey);\n    }\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  void onLoadFailedInternal(LoadData<?> loadData, @NonNull Exception e) {\n    cb.onDataFetcherFailed(originalKey, e, loadData.fetcher, loadData.fetcher.getDataSource());\n  }\n\n  @Override\n  public void reschedule() {\n    // We don't expect this to happen, although if we ever need it to we can delegate to our\n    // callback.\n    throw new UnsupportedOperationException();\n  }\n\n  // Called from source cache generator.\n  @Override\n  public void onDataFetcherReady(\n      Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {\n    // This data fetcher will be loading from a File and provide the wrong data source, so override\n    // with the data source of the original fetcher\n    cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);\n  }\n\n  @Override\n  public void onDataFetcherFailed(\n      Key sourceKey, Exception e, DataFetcher<?> fetcher, DataSource dataSource) {\n    cb.onDataFetcherFailed(sourceKey, e, fetcher, loadData.fetcher.getDataSource());\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/ArrayAdapterInterface.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\n/**\n * Interface for handling operations on a primitive array type.\n *\n * @param <T> Array type (e.g. byte[], int[])\n */\ninterface ArrayAdapterInterface<T> {\n\n  /** TAG for logging. */\n  String getTag();\n\n  /** Return the length of the given array. */\n  int getArrayLength(T array);\n\n  /** Allocate and return an array of the specified size. */\n  T newArray(int length);\n\n  /** Return the size of an element in the array in bytes (e.g. for int return 4). */\n  int getElementSizeInBytes();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/ArrayPool.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\n/** Interface for an array pool that pools arrays of different types. */\npublic interface ArrayPool {\n  /**\n   * A standard size to use to increase hit rates when the required size isn't defined. Currently\n   * 64KB.\n   */\n  int STANDARD_BUFFER_SIZE_BYTES = 64 * 1024;\n\n  /**\n   * Optionally adds the given array of the given type to the pool.\n   *\n   * <p>Arrays may be ignored, for example if the array is larger than the maximum size of the pool.\n   *\n   * @deprecated Use {@link #put(Object)}\n   */\n  @Deprecated\n  <T> void put(T array, Class<T> arrayClass);\n\n  /**\n   * Optionally adds the given array of the given type to the pool.\n   *\n   * <p>Arrays may be ignored, for example if the array is larger than the maximum size of the pool.\n   */\n  <T> void put(T array);\n\n  /**\n   * Returns a non-null array of the given type with a length {@code >=} to the given size.\n   *\n   * <p>If an array of the given size isn't in the pool, a new one will be allocated.\n   *\n   * <p>This class makes no guarantees about the contents of the returned array.\n   *\n   * @see #getExact(int, Class)\n   */\n  <T> T get(int size, Class<T> arrayClass);\n\n  /**\n   * Returns a non-null array of the given type with a length exactly equal to the given size.\n   *\n   * <p>If an array of the given size isn't in the pool, a new one will be allocated.\n   *\n   * <p>This class makes no guarantees about the contents of the returned array.\n   *\n   * @see #get(int, Class)\n   */\n  <T> T getExact(int size, Class<T> arrayClass);\n\n  /** Clears all arrays from the pool. */\n  void clearMemory();\n\n  /**\n   * Trims the size to the appropriate level.\n   *\n   * @param level A trim specified in {@link android.content.ComponentCallbacks2}.\n   */\n  void trimMemory(int level);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/AttributeStrategy.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\n\n/**\n * A strategy for reusing bitmaps that requires any returned bitmap's dimensions to exactly match\n * those request.\n */\nclass AttributeStrategy implements LruPoolStrategy {\n  private final KeyPool keyPool = new KeyPool();\n  private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();\n\n  @Override\n  public void put(Bitmap bitmap) {\n    final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());\n\n    groupedMap.put(key, bitmap);\n  }\n\n  @Override\n  public Bitmap get(int width, int height, Bitmap.Config config) {\n    final Key key = keyPool.get(width, height, config);\n\n    return groupedMap.get(key);\n  }\n\n  @Override\n  public Bitmap removeLast() {\n    return groupedMap.removeLast();\n  }\n\n  @Override\n  public String logBitmap(Bitmap bitmap) {\n    return getBitmapString(bitmap);\n  }\n\n  @Override\n  public String logBitmap(int width, int height, Bitmap.Config config) {\n    return getBitmapString(width, height, config);\n  }\n\n  @Override\n  public int getSize(Bitmap bitmap) {\n    return Util.getBitmapByteSize(bitmap);\n  }\n\n  @Override\n  public String toString() {\n    return \"AttributeStrategy:\\n  \" + groupedMap;\n  }\n\n  private static String getBitmapString(Bitmap bitmap) {\n    return getBitmapString(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  static String getBitmapString(int width, int height, Bitmap.Config config) {\n    return \"[\" + width + \"x\" + height + \"], \" + config;\n  }\n\n  @VisibleForTesting\n  static class KeyPool extends BaseKeyPool<Key> {\n    Key get(int width, int height, Bitmap.Config config) {\n      Key result = get();\n      result.init(width, height, config);\n      return result;\n    }\n\n    @Override\n    protected Key create() {\n      return new Key(this);\n    }\n  }\n\n  @VisibleForTesting\n  static class Key implements Poolable {\n    private final KeyPool pool;\n    private int width;\n    private int height;\n    // Config can be null :(\n    private Bitmap.Config config;\n\n    public Key(KeyPool pool) {\n      this.pool = pool;\n    }\n\n    public void init(int width, int height, Bitmap.Config config) {\n      this.width = width;\n      this.height = height;\n      this.config = config;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof Key) {\n        Key other = (Key) o;\n        return width == other.width && height == other.height && config == other.config;\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      int result = width;\n      result = 31 * result + height;\n      result = 31 * result + (config != null ? config.hashCode() : 0);\n      return result;\n    }\n\n    @Override\n    public String toString() {\n      return getBitmapString(width, height, config);\n    }\n\n    @Override\n    public void offer() {\n      pool.offer(this);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BaseKeyPool.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport com.bumptech.glide.util.Util;\nimport java.util.Queue;\n\nabstract class BaseKeyPool<T extends Poolable> {\n  private static final int MAX_SIZE = 20;\n  private final Queue<T> keyPool = Util.createQueue(MAX_SIZE);\n\n  T get() {\n    T result = keyPool.poll();\n    if (result == null) {\n      result = create();\n    }\n    return result;\n  }\n\n  public void offer(T key) {\n    if (keyPool.size() < MAX_SIZE) {\n      keyPool.offer(key);\n    }\n  }\n\n  abstract T create();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPool.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\n\n/** An interface for a pool that allows users to reuse {@link android.graphics.Bitmap} objects. */\npublic interface BitmapPool {\n\n  /** Returns the current maximum size of the pool in bytes. */\n  long getMaxSize();\n\n  /**\n   * Multiplies the initial size of the pool by the given multiplier to dynamically and\n   * synchronously allow users to adjust the size of the pool.\n   *\n   * <p>If the current total size of the pool is larger than the max size after the given multiplier\n   * is applied, {@link Bitmap}s should be evicted until the pool is smaller than the new max size.\n   *\n   * @param sizeMultiplier The size multiplier to apply between 0 and 1.\n   */\n  void setSizeMultiplier(float sizeMultiplier);\n\n  /**\n   * Adds the given {@link android.graphics.Bitmap} if it is eligible to be re-used and the pool can\n   * fit it, or calls {@link Bitmap#recycle()} on the Bitmap and discards it.\n   *\n   * <p>Callers must <em>not</em> continue to use the Bitmap after calling this method.\n   *\n   * @param bitmap The {@link android.graphics.Bitmap} to attempt to add.\n   * @see android.graphics.Bitmap#isMutable()\n   * @see android.graphics.Bitmap#recycle()\n   */\n  void put(Bitmap bitmap);\n\n  /**\n   * Returns a {@link android.graphics.Bitmap} of exactly the given width, height, and\n   * configuration, and containing only transparent pixels.\n   *\n   * <p>If no Bitmap with the requested attributes is present in the pool, a new one will be\n   * allocated.\n   *\n   * <p>Because this method erases all pixels in the {@link Bitmap}, this method is slightly slower\n   * than {@link #getDirty(int, int, android.graphics.Bitmap.Config)}. If the {@link\n   * android.graphics.Bitmap} is being obtained to be used in {@link android.graphics.BitmapFactory}\n   * or in any other case where every pixel in the {@link android.graphics.Bitmap} will always be\n   * overwritten or cleared, {@link #getDirty(int, int, android.graphics.Bitmap.Config)} will be\n   * faster. When in doubt, use this method to ensure correctness.\n   *\n   * <pre>\n   *     Implementations can should clear out every returned Bitmap using the following:\n   *\n   * {@code\n   * bitmap.eraseColor(Color.TRANSPARENT);\n   * }\n   * </pre>\n   *\n   * @param width The width in pixels of the desired {@link android.graphics.Bitmap}.\n   * @param height The height in pixels of the desired {@link android.graphics.Bitmap}.\n   * @param config The {@link android.graphics.Bitmap.Config} of the desired {@link\n   *     android.graphics.Bitmap}.\n   * @see #getDirty(int, int, android.graphics.Bitmap.Config)\n   */\n  @NonNull\n  Bitmap get(int width, int height, Bitmap.Config config);\n\n  /**\n   * Identical to {@link #get(int, int, android.graphics.Bitmap.Config)} except that any returned\n   * {@link android.graphics.Bitmap} may <em>not</em> have been erased and may contain random data.\n   *\n   * <p>If no Bitmap with the requested attributes is present in the pool, a new one will be\n   * allocated.\n   *\n   * <p>Although this method is slightly more efficient than {@link #get(int, int,\n   * android.graphics.Bitmap.Config)} it should be used with caution and only when the caller is\n   * sure that they are going to erase the {@link android.graphics.Bitmap} entirely before writing\n   * new data to it.\n   *\n   * @param width The width in pixels of the desired {@link android.graphics.Bitmap}.\n   * @param height The height in pixels of the desired {@link android.graphics.Bitmap}.\n   * @param config The {@link android.graphics.Bitmap.Config} of the desired {@link\n   *     android.graphics.Bitmap}.\n   * @return A {@link android.graphics.Bitmap} with exactly the given width, height, and config\n   *     potentially containing random image data.\n   * @see #get(int, int, android.graphics.Bitmap.Config)\n   */\n  @NonNull\n  Bitmap getDirty(int width, int height, Bitmap.Config config);\n\n  /** Removes all {@link android.graphics.Bitmap}s from the pool. */\n  void clearMemory();\n\n  /**\n   * Reduces the size of the cache by evicting items based on the given level.\n   *\n   * @param level The level from {@link android.content.ComponentCallbacks2} to use to determine how\n   *     many {@link android.graphics.Bitmap}s to evict.\n   * @see android.content.ComponentCallbacks2\n   */\n  void trimMemory(int level);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPoolAdapter.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\n\n/**\n * An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool BitmapPool} implementation\n * that rejects all {@link android.graphics.Bitmap Bitmap}s added to it and always returns a new\n * {@link android.graphics.Bitmap Bitmap} from {@link #get}.\n */\npublic class BitmapPoolAdapter implements BitmapPool {\n  @Override\n  public long getMaxSize() {\n    return 0;\n  }\n\n  @Override\n  public void setSizeMultiplier(float sizeMultiplier) {\n    // Do nothing.\n  }\n\n  @Override\n  public void put(Bitmap bitmap) {\n    bitmap.recycle();\n  }\n\n  @NonNull\n  @Override\n  public Bitmap get(int width, int height, Bitmap.Config config) {\n    return Bitmap.createBitmap(width, height, config);\n  }\n\n  @NonNull\n  @Override\n  public Bitmap getDirty(int width, int height, Bitmap.Config config) {\n    return get(width, height, config);\n  }\n\n  @Override\n  public void clearMemory() {\n    // Do nothing.\n  }\n\n  @Override\n  public void trimMemory(int level) {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/ByteArrayAdapter.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\n/** Adapter for handling primitive byte arrays. */\n@SuppressWarnings(\"PMD.UseVarargs\")\npublic final class ByteArrayAdapter implements ArrayAdapterInterface<byte[]> {\n  private static final String TAG = \"ByteArrayPool\";\n\n  @Override\n  public String getTag() {\n    return TAG;\n  }\n\n  @Override\n  public int getArrayLength(byte[] array) {\n    return array.length;\n  }\n\n  @Override\n  public byte[] newArray(int length) {\n    return new byte[length];\n  }\n\n  @Override\n  public int getElementSizeInBytes() {\n    return 1;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/GroupedLinkedMap.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Similar to {@link java.util.LinkedHashMap} when access ordered except that it is access ordered\n * on groups of bitmaps rather than individual objects. The idea is to be able to find the LRU\n * bitmap size, rather than the LRU bitmap object. We can then remove bitmaps from the least\n * recently used size of bitmap when we need to reduce our cache size.\n *\n * <p>For the purposes of the LRU, we count gets for a particular size of bitmap as an access, even\n * if no bitmaps of that size are present. We do not count addition or removal of bitmaps as an\n * access.\n */\nclass GroupedLinkedMap<K extends Poolable, V> {\n  private final LinkedEntry<K, V> head = new LinkedEntry<>();\n  private final Map<K, LinkedEntry<K, V>> keyToEntry = new HashMap<>();\n\n  public void put(K key, V value) {\n    LinkedEntry<K, V> entry = keyToEntry.get(key);\n\n    if (entry == null) {\n      entry = new LinkedEntry<>(key);\n      makeTail(entry);\n      keyToEntry.put(key, entry);\n    } else {\n      key.offer();\n    }\n\n    entry.add(value);\n  }\n\n  @Nullable\n  public V get(K key) {\n    LinkedEntry<K, V> entry = keyToEntry.get(key);\n    if (entry == null) {\n      entry = new LinkedEntry<>(key);\n      keyToEntry.put(key, entry);\n    } else {\n      key.offer();\n    }\n\n    makeHead(entry);\n\n    return entry.removeLast();\n  }\n\n  @Nullable\n  public V removeLast() {\n    LinkedEntry<K, V> last = head.prev;\n\n    while (!last.equals(head)) {\n      V removed = last.removeLast();\n      if (removed != null) {\n        return removed;\n      } else {\n        // We will clean up empty lru entries since they are likely to have been one off or\n        // unusual sizes and\n        // are not likely to be requested again so the gc thrash should be minimal. Doing so will\n        // speed up our\n        // removeLast operation in the future and prevent our linked list from growing to\n        // arbitrarily large\n        // sizes.\n        removeEntry(last);\n        keyToEntry.remove(last.key);\n        last.key.offer();\n      }\n\n      last = last.prev;\n    }\n\n    return null;\n  }\n\n  @Override\n  public String toString() {\n    StringBuilder sb = new StringBuilder(\"GroupedLinkedMap( \");\n    LinkedEntry<K, V> current = head.next;\n    boolean hadAtLeastOneItem = false;\n    while (!current.equals(head)) {\n      hadAtLeastOneItem = true;\n      sb.append('{').append(current.key).append(':').append(current.size()).append(\"}, \");\n      current = current.next;\n    }\n    if (hadAtLeastOneItem) {\n      sb.delete(sb.length() - 2, sb.length());\n    }\n    return sb.append(\" )\").toString();\n  }\n\n  // Make the entry the most recently used item.\n  private void makeHead(LinkedEntry<K, V> entry) {\n    removeEntry(entry);\n    entry.prev = head;\n    entry.next = head.next;\n    updateEntry(entry);\n  }\n\n  // Make the entry the least recently used item.\n  private void makeTail(LinkedEntry<K, V> entry) {\n    removeEntry(entry);\n    entry.prev = head.prev;\n    entry.next = head;\n    updateEntry(entry);\n  }\n\n  private static <K, V> void updateEntry(LinkedEntry<K, V> entry) {\n    entry.next.prev = entry;\n    entry.prev.next = entry;\n  }\n\n  private static <K, V> void removeEntry(LinkedEntry<K, V> entry) {\n    entry.prev.next = entry.next;\n    entry.next.prev = entry.prev;\n  }\n\n  private static class LinkedEntry<K, V> {\n    @Synthetic final K key;\n    private List<V> values;\n    LinkedEntry<K, V> next;\n    LinkedEntry<K, V> prev;\n\n    // Used only for the first item in the list which we will treat specially and which will not\n    // contain a value.\n    LinkedEntry() {\n      this(null);\n    }\n\n    LinkedEntry(K key) {\n      next = prev = this;\n      this.key = key;\n    }\n\n    @Nullable\n    public V removeLast() {\n      final int valueSize = size();\n      return valueSize > 0 ? values.remove(valueSize - 1) : null;\n    }\n\n    public int size() {\n      return values != null ? values.size() : 0;\n    }\n\n    public void add(V value) {\n      if (values == null) {\n        values = new ArrayList<>();\n      }\n      values.add(value);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/IntegerArrayAdapter.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\n/** Adapter for handling primitive int arrays. */\n@SuppressWarnings(\"PMD.UseVarargs\")\npublic final class IntegerArrayAdapter implements ArrayAdapterInterface<int[]> {\n  private static final String TAG = \"IntegerArrayPool\";\n\n  @Override\n  public String getTag() {\n    return TAG;\n  }\n\n  @Override\n  public int getArrayLength(int[] array) {\n    return array.length;\n  }\n\n  @Override\n  public int[] newArray(int length) {\n    return new int[length];\n  }\n\n  @Override\n  public int getElementSizeInBytes() {\n    return 4;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruArrayPool.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.util.Log;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.TreeMap;\n\n/**\n * A fixed size Array Pool that evicts arrays using an LRU strategy to keep the pool under the\n * maximum byte size.\n */\npublic final class LruArrayPool implements ArrayPool {\n  // 4MB.\n  private static final int DEFAULT_SIZE = 4 * 1024 * 1024;\n\n  /**\n   * The maximum number of times larger an int array may be to be than a requested size to eligible\n   * to be returned from the pool.\n   */\n  @VisibleForTesting static final int MAX_OVER_SIZE_MULTIPLE = 8;\n\n  /** Used to calculate the maximum % of the total pool size a single byte array may consume. */\n  private static final int SINGLE_ARRAY_MAX_SIZE_DIVISOR = 2;\n\n  private final GroupedLinkedMap<Key, Object> groupedMap = new GroupedLinkedMap<>();\n  private final KeyPool keyPool = new KeyPool();\n  private final Map<Class<?>, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();\n  private final Map<Class<?>, ArrayAdapterInterface<?>> adapters = new HashMap<>();\n  private final int maxSize;\n  private int currentSize;\n\n  @VisibleForTesting\n  public LruArrayPool() {\n    maxSize = DEFAULT_SIZE;\n  }\n\n  /**\n   * Constructor for a new pool.\n   *\n   * @param maxSize The maximum size in integers of the pool.\n   */\n  public LruArrayPool(int maxSize) {\n    this.maxSize = maxSize;\n  }\n\n  @Deprecated\n  @Override\n  public <T> void put(T array, Class<T> arrayClass) {\n    put(array);\n  }\n\n  @Override\n  public synchronized <T> void put(T array) {\n    @SuppressWarnings(\"unchecked\")\n    Class<T> arrayClass = (Class<T>) array.getClass();\n\n    ArrayAdapterInterface<T> arrayAdapter = getAdapterFromType(arrayClass);\n    int size = arrayAdapter.getArrayLength(array);\n    int arrayBytes = size * arrayAdapter.getElementSizeInBytes();\n    if (!isSmallEnoughForReuse(arrayBytes)) {\n      return;\n    }\n    Key key = keyPool.get(size, arrayClass);\n\n    groupedMap.put(key, array);\n    NavigableMap<Integer, Integer> sizes = getSizesForAdapter(arrayClass);\n    Integer current = sizes.get(key.size);\n    sizes.put(key.size, current == null ? 1 : current + 1);\n    currentSize += arrayBytes;\n    evict();\n  }\n\n  @Override\n  public synchronized <T> T getExact(int size, Class<T> arrayClass) {\n    Key key = keyPool.get(size, arrayClass);\n    return getForKey(key, arrayClass);\n  }\n\n  @Override\n  public synchronized <T> T get(int size, Class<T> arrayClass) {\n    Integer possibleSize = getSizesForAdapter(arrayClass).ceilingKey(size);\n    final Key key;\n    if (mayFillRequest(size, possibleSize)) {\n      key = keyPool.get(possibleSize, arrayClass);\n    } else {\n      key = keyPool.get(size, arrayClass);\n    }\n    return getForKey(key, arrayClass);\n  }\n\n  private <T> T getForKey(Key key, Class<T> arrayClass) {\n    ArrayAdapterInterface<T> arrayAdapter = getAdapterFromType(arrayClass);\n    T result = getArrayForKey(key);\n    if (result != null) {\n      currentSize -= arrayAdapter.getArrayLength(result) * arrayAdapter.getElementSizeInBytes();\n      decrementArrayOfSize(arrayAdapter.getArrayLength(result), arrayClass);\n    }\n\n    if (result == null) {\n      if (Log.isLoggable(arrayAdapter.getTag(), Log.VERBOSE)) {\n        Log.v(arrayAdapter.getTag(), \"Allocated \" + key.size + \" bytes\");\n      }\n      result = arrayAdapter.newArray(key.size);\n    }\n    return result;\n  }\n\n  // Our cast is safe because the Key is based on the type.\n  @SuppressWarnings({\"unchecked\", \"TypeParameterUnusedInFormals\"})\n  @Nullable\n  private <T> T getArrayForKey(Key key) {\n    return (T) groupedMap.get(key);\n  }\n\n  private boolean isSmallEnoughForReuse(int byteSize) {\n    return byteSize <= maxSize / SINGLE_ARRAY_MAX_SIZE_DIVISOR;\n  }\n\n  private boolean mayFillRequest(int requestedSize, Integer actualSize) {\n    return actualSize != null\n        && (isNoMoreThanHalfFull() || actualSize <= (MAX_OVER_SIZE_MULTIPLE * requestedSize));\n  }\n\n  private boolean isNoMoreThanHalfFull() {\n    return currentSize == 0 || maxSize / currentSize >= 2;\n  }\n\n  @Override\n  public synchronized void clearMemory() {\n    evictToSize(0);\n  }\n\n  @Override\n  public synchronized void trimMemory(int level) {\n    if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {\n      clearMemory();\n    } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN\n        || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {\n      evictToSize(maxSize / 2);\n    }\n  }\n\n  private void evict() {\n    evictToSize(maxSize);\n  }\n\n  private void evictToSize(int size) {\n    while (currentSize > size) {\n      Object evicted = groupedMap.removeLast();\n      Preconditions.checkNotNull(evicted);\n      ArrayAdapterInterface<Object> arrayAdapter = getAdapterFromObject(evicted);\n      currentSize -= arrayAdapter.getArrayLength(evicted) * arrayAdapter.getElementSizeInBytes();\n      decrementArrayOfSize(arrayAdapter.getArrayLength(evicted), evicted.getClass());\n      if (Log.isLoggable(arrayAdapter.getTag(), Log.VERBOSE)) {\n        Log.v(arrayAdapter.getTag(), \"evicted: \" + arrayAdapter.getArrayLength(evicted));\n      }\n    }\n  }\n\n  private void decrementArrayOfSize(int size, Class<?> arrayClass) {\n    NavigableMap<Integer, Integer> sizes = getSizesForAdapter(arrayClass);\n    Integer current = sizes.get(size);\n    if (current == null) {\n      throw new NullPointerException(\n          \"Tried to decrement empty size\" + \", size: \" + size + \", this: \" + this);\n    }\n    if (current == 1) {\n      sizes.remove(size);\n    } else {\n      sizes.put(size, current - 1);\n    }\n  }\n\n  private NavigableMap<Integer, Integer> getSizesForAdapter(Class<?> arrayClass) {\n    NavigableMap<Integer, Integer> sizes = sortedSizes.get(arrayClass);\n    if (sizes == null) {\n      sizes = new TreeMap<>();\n      sortedSizes.put(arrayClass, sizes);\n    }\n    return sizes;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private <T> ArrayAdapterInterface<T> getAdapterFromObject(T object) {\n    return (ArrayAdapterInterface<T>) getAdapterFromType(object.getClass());\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private <T> ArrayAdapterInterface<T> getAdapterFromType(Class<T> arrayPoolClass) {\n    ArrayAdapterInterface<?> adapter = adapters.get(arrayPoolClass);\n    if (adapter == null) {\n      if (arrayPoolClass.equals(int[].class)) {\n        adapter = new IntegerArrayAdapter();\n      } else if (arrayPoolClass.equals(byte[].class)) {\n        adapter = new ByteArrayAdapter();\n      } else {\n        throw new IllegalArgumentException(\n            \"No array pool found for: \" + arrayPoolClass.getSimpleName());\n      }\n      adapters.put(arrayPoolClass, adapter);\n    }\n    return (ArrayAdapterInterface<T>) adapter;\n  }\n\n  // VisibleForTesting\n  int getCurrentSize() {\n    int currentSize = 0;\n    for (Class<?> type : sortedSizes.keySet()) {\n      for (Integer size : sortedSizes.get(type).keySet()) {\n        ArrayAdapterInterface<?> adapter = getAdapterFromType(type);\n        currentSize += size * sortedSizes.get(type).get(size) * adapter.getElementSizeInBytes();\n      }\n    }\n    return currentSize;\n  }\n\n  private static final class KeyPool extends BaseKeyPool<Key> {\n\n    @Synthetic\n    KeyPool() {}\n\n    Key get(int size, Class<?> arrayClass) {\n      Key result = get();\n      result.init(size, arrayClass);\n      return result;\n    }\n\n    @Override\n    protected Key create() {\n      return new Key(this);\n    }\n  }\n\n  private static final class Key implements Poolable {\n    private final KeyPool pool;\n    @Synthetic int size;\n    private Class<?> arrayClass;\n\n    Key(KeyPool pool) {\n      this.pool = pool;\n    }\n\n    void init(int length, Class<?> arrayClass) {\n      this.size = length;\n      this.arrayClass = arrayClass;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof Key) {\n        Key other = (Key) o;\n        return size == other.size && arrayClass == other.arrayClass;\n      }\n      return false;\n    }\n\n    @Override\n    public String toString() {\n      return \"Key{\" + \"size=\" + size + \"array=\" + arrayClass + '}';\n    }\n\n    @Override\n    public void offer() {\n      pool.offer(this);\n    }\n\n    @Override\n    public int hashCode() {\n      int result = size;\n      result = 31 * result + (arrayClass != null ? arrayClass.hashCode() : 0);\n      return result;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.content.ComponentCallbacks2;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.os.Build;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation that uses an\n * {@link com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy} to bucket {@link Bitmap}s\n * and then uses an LRU eviction policy to evict {@link android.graphics.Bitmap}s from the least\n * recently used bucket in order to keep the pool below a given maximum size limit.\n */\npublic class LruBitmapPool implements BitmapPool {\n  private static final String TAG = \"LruBitmapPool\";\n  private static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888;\n\n  private final LruPoolStrategy strategy;\n  private final Set<Bitmap.Config> allowedConfigs;\n  private final long initialMaxSize;\n  private final BitmapTracker tracker;\n\n  private long maxSize;\n  private long currentSize;\n  private int hits;\n  private int misses;\n  private int puts;\n  private int evictions;\n\n  // Exposed for testing only.\n  LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {\n    this.initialMaxSize = maxSize;\n    this.maxSize = maxSize;\n    this.strategy = strategy;\n    this.allowedConfigs = allowedConfigs;\n    this.tracker = new NullBitmapTracker();\n  }\n\n  /**\n   * Constructor for LruBitmapPool.\n   *\n   * @param maxSize The initial maximum size of the pool in bytes.\n   */\n  public LruBitmapPool(long maxSize) {\n    this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());\n  }\n\n  /**\n   * Constructor for LruBitmapPool.\n   *\n   * @param maxSize The initial maximum size of the pool in bytes.\n   * @param allowedConfigs A white listed put of {@link android.graphics.Bitmap.Config} that are\n   *     allowed to be put into the pool. Configs not in the allowed put will be rejected.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public LruBitmapPool(long maxSize, Set<Bitmap.Config> allowedConfigs) {\n    this(maxSize, getDefaultStrategy(), allowedConfigs);\n  }\n\n  /** Returns the number of cache hits for bitmaps in the pool. */\n  public long hitCount() {\n    return hits;\n  }\n\n  /** Returns the number of cache misses for bitmaps in the pool. */\n  public long missCount() {\n    return misses;\n  }\n\n  /** Returns the number of bitmaps that have been evicted from the pool. */\n  public long evictionCount() {\n    return evictions;\n  }\n\n  /** Returns the current size of the pool in bytes. */\n  public long getCurrentSize() {\n    return currentSize;\n  }\n\n  @Override\n  public long getMaxSize() {\n    return maxSize;\n  }\n\n  @Override\n  public synchronized void setSizeMultiplier(float sizeMultiplier) {\n    maxSize = Math.round(initialMaxSize * sizeMultiplier);\n    evict();\n  }\n\n  @Override\n  public synchronized void put(Bitmap bitmap) {\n    if (bitmap == null) {\n      throw new NullPointerException(\"Bitmap must not be null\");\n    }\n    if (bitmap.isRecycled()) {\n      throw new IllegalStateException(\"Cannot pool recycled bitmap\");\n    }\n    if (!bitmap.isMutable()\n        || strategy.getSize(bitmap) > maxSize\n        || !allowedConfigs.contains(bitmap.getConfig())) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(\n            TAG,\n            \"Reject bitmap from pool\"\n                + \", bitmap: \"\n                + strategy.logBitmap(bitmap)\n                + \", is mutable: \"\n                + bitmap.isMutable()\n                + \", is allowed config: \"\n                + allowedConfigs.contains(bitmap.getConfig()));\n      }\n      bitmap.recycle();\n      return;\n    }\n\n    final int size = strategy.getSize(bitmap);\n    strategy.put(bitmap);\n    tracker.add(bitmap);\n\n    puts++;\n    currentSize += size;\n\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(TAG, \"Put bitmap in pool=\" + strategy.logBitmap(bitmap));\n    }\n    dump();\n\n    evict();\n  }\n\n  private void evict() {\n    trimToSize(maxSize);\n  }\n\n  @Override\n  @NonNull\n  public Bitmap get(int width, int height, Bitmap.Config config) {\n    Bitmap result = getDirtyOrNull(width, height, config);\n    if (result != null) {\n      // Bitmaps in the pool contain random data that in some cases must be cleared for an image\n      // to be rendered correctly. we shouldn't force all consumers to independently erase the\n      // contents individually, so we do so here. See issue #131.\n      result.eraseColor(Color.TRANSPARENT);\n    } else {\n      result = createBitmap(width, height, config);\n    }\n\n    return result;\n  }\n\n  @NonNull\n  @Override\n  public Bitmap getDirty(int width, int height, Bitmap.Config config) {\n    Bitmap result = getDirtyOrNull(width, height, config);\n    if (result == null) {\n      result = createBitmap(width, height, config);\n    }\n    return result;\n  }\n\n  @NonNull\n  private static Bitmap createBitmap(int width, int height, @Nullable Bitmap.Config config) {\n    return Bitmap.createBitmap(width, height, config != null ? config : DEFAULT_CONFIG);\n  }\n\n  @TargetApi(Build.VERSION_CODES.O)\n  private static void assertNotHardwareConfig(Bitmap.Config config) {\n    // Avoid short circuiting on sdk int since it breaks on some versions of Android.\n    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n      return;\n    }\n\n    if (config == Bitmap.Config.HARDWARE) {\n      throw new IllegalArgumentException(\n          \"Cannot create a mutable Bitmap with config: \"\n              + config\n              + \". Consider setting Downsampler#ALLOW_HARDWARE_CONFIG to false in your\"\n              + \" RequestOptions and/or in GlideBuilder.setDefaultRequestOptions\");\n    }\n  }\n\n  @Nullable\n  private synchronized Bitmap getDirtyOrNull(\n      int width, int height, @Nullable Bitmap.Config config) {\n    assertNotHardwareConfig(config);\n    // Config will be null for non public config types, which can lead to transformations naively\n    // passing in null as the requested config here. See issue #194.\n    final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);\n    if (result == null) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Missing bitmap=\" + strategy.logBitmap(width, height, config));\n      }\n      misses++;\n    } else {\n      hits++;\n      currentSize -= strategy.getSize(result);\n      tracker.remove(result);\n      normalize(result);\n    }\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(TAG, \"Get bitmap=\" + strategy.logBitmap(width, height, config));\n    }\n    dump();\n\n    return result;\n  }\n\n  // Setting these two values provides Bitmaps that are essentially equivalent to those returned\n  // from Bitmap.createBitmap.\n  private static void normalize(Bitmap bitmap) {\n    bitmap.setHasAlpha(true);\n    maybeSetPreMultiplied(bitmap);\n  }\n\n  @TargetApi(Build.VERSION_CODES.KITKAT)\n  private static void maybeSetPreMultiplied(Bitmap bitmap) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n      bitmap.setPremultiplied(true);\n    }\n  }\n\n  @Override\n  public void clearMemory() {\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      Log.d(TAG, \"clearMemory\");\n    }\n    trimToSize(0);\n  }\n\n  @SuppressWarnings(\"checkstyle:UnnecessaryParentheses\") // Readability\n  @SuppressLint(\"InlinedApi\")\n  @Override\n  public void trimMemory(int level) {\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      Log.d(TAG, \"trimMemory, level=\" + level);\n    }\n    if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND\n        || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M\n            && level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)) {\n      clearMemory();\n    } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN\n        || level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {\n      trimToSize(getMaxSize() / 2);\n    }\n  }\n\n  private synchronized void trimToSize(long size) {\n    while (currentSize > size) {\n      final Bitmap removed = strategy.removeLast();\n      // TODO: This shouldn't ever happen, see #331.\n      if (removed == null) {\n        if (Log.isLoggable(TAG, Log.WARN)) {\n          Log.w(TAG, \"Size mismatch, resetting\");\n          dumpUnchecked();\n        }\n        currentSize = 0;\n        return;\n      }\n      tracker.remove(removed);\n      currentSize -= strategy.getSize(removed);\n      evictions++;\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Evicting bitmap=\" + strategy.logBitmap(removed));\n      }\n      dump();\n      removed.recycle();\n    }\n  }\n\n  private void dump() {\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      dumpUnchecked();\n    }\n  }\n\n  private void dumpUnchecked() {\n    Log.v(\n        TAG,\n        \"Hits=\"\n            + hits\n            + \", misses=\"\n            + misses\n            + \", puts=\"\n            + puts\n            + \", evictions=\"\n            + evictions\n            + \", currentSize=\"\n            + currentSize\n            + \", maxSize=\"\n            + maxSize\n            + \"\\nStrategy=\"\n            + strategy);\n  }\n\n  private static LruPoolStrategy getDefaultStrategy() {\n    final LruPoolStrategy strategy;\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n      strategy = new SizeConfigStrategy();\n    } else {\n      strategy = new AttributeStrategy();\n    }\n    return strategy;\n  }\n\n  @TargetApi(Build.VERSION_CODES.O)\n  private static Set<Bitmap.Config> getDefaultAllowedConfigs() {\n    Set<Bitmap.Config> configs = new HashSet<>(Arrays.asList(Bitmap.Config.values()));\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n      // GIFs, among other types, end up with a native Bitmap config that doesn't map to a java\n      // config and is treated as null in java code. On KitKat+ these Bitmaps can be reconfigured\n      // and are suitable for re-use.\n      configs.add(null);\n    }\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n      configs.remove(Bitmap.Config.HARDWARE);\n    }\n    return Collections.unmodifiableSet(configs);\n  }\n\n  private interface BitmapTracker {\n    void add(Bitmap bitmap);\n\n    void remove(Bitmap bitmap);\n  }\n\n  @SuppressWarnings(\"unused\")\n  // Only used for debugging\n  private static class ThrowingBitmapTracker implements BitmapTracker {\n    private final Set<Bitmap> bitmaps = Collections.synchronizedSet(new HashSet<Bitmap>());\n\n    @Override\n    public void add(Bitmap bitmap) {\n      if (bitmaps.contains(bitmap)) {\n        throw new IllegalStateException(\n            \"Can't add already added bitmap: \"\n                + bitmap\n                + \" [\"\n                + bitmap.getWidth()\n                + \"x\"\n                + bitmap.getHeight()\n                + \"]\");\n      }\n      bitmaps.add(bitmap);\n    }\n\n    @Override\n    public void remove(Bitmap bitmap) {\n      if (!bitmaps.contains(bitmap)) {\n        throw new IllegalStateException(\"Cannot remove bitmap not in tracker\");\n      }\n      bitmaps.remove(bitmap);\n    }\n  }\n\n  private static final class NullBitmapTracker implements BitmapTracker {\n\n    @Synthetic\n    NullBitmapTracker() {}\n\n    @Override\n    public void add(Bitmap bitmap) {\n      // Do nothing.\n    }\n\n    @Override\n    public void remove(Bitmap bitmap) {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruPoolStrategy.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.Nullable;\n\ninterface LruPoolStrategy {\n  void put(Bitmap bitmap);\n\n  @Nullable\n  Bitmap get(int width, int height, Bitmap.Config config);\n\n  @Nullable\n  Bitmap removeLast();\n\n  String logBitmap(Bitmap bitmap);\n\n  String logBitmap(int width, int height, Bitmap.Config config);\n\n  int getSize(Bitmap bitmap);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/Poolable.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\ninterface Poolable {\n  void offer();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/PrettyPrintTreeMap.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport java.util.TreeMap;\n\n// Never serialized.\n@SuppressWarnings(\"serial\")\nclass PrettyPrintTreeMap<K, V> extends TreeMap<K, V> {\n  @Override\n  public String toString() {\n    StringBuilder sb = new StringBuilder();\n    sb.append(\"( \");\n    for (Entry<K, V> entry : entrySet()) {\n      sb.append('{').append(entry.getKey()).append(':').append(entry.getValue()).append(\"}, \");\n    }\n    if (!isEmpty()) {\n      sb.replace(sb.length() - 2, sb.length(), \"\");\n    }\n    return sb.append(\" )\").toString();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SizeConfigStrategy.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\nimport android.os.Build;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.TreeMap;\n\n/**\n * Keys {@link android.graphics.Bitmap Bitmaps} using both {@link\n * android.graphics.Bitmap#getAllocationByteCount()} and the {@link android.graphics.Bitmap.Config}\n * returned from {@link android.graphics.Bitmap#getConfig()}.\n *\n * <p>Using both the config and the byte size allows us to safely re-use a greater variety of {@link\n * android.graphics.Bitmap Bitmaps}, which increases the hit rate of the pool and therefore the\n * performance of applications. This class works around #301 by only allowing re-use of {@link\n * android.graphics.Bitmap Bitmaps} with a matching number of bytes per pixel.\n */\n@RequiresApi(Build.VERSION_CODES.KITKAT)\npublic class SizeConfigStrategy implements LruPoolStrategy {\n  private static final int MAX_SIZE_MULTIPLE = 8;\n\n  private static final Bitmap.Config[] ARGB_8888_IN_CONFIGS;\n\n  static {\n    Bitmap.Config[] result =\n        new Bitmap.Config[] {\n          Bitmap.Config.ARGB_8888,\n          // The value returned by Bitmaps with the hidden Bitmap config.\n          null,\n        };\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n      result = Arrays.copyOf(result, result.length + 1);\n      result[result.length - 1] = Config.RGBA_F16;\n    }\n    ARGB_8888_IN_CONFIGS = result;\n  }\n\n  private static final Bitmap.Config[] RGBA_F16_IN_CONFIGS = ARGB_8888_IN_CONFIGS;\n\n  // We probably could allow ARGB_4444 and RGB_565 to decode into each other, but ARGB_4444 is\n  // deprecated and we'd rather be safe.\n  private static final Bitmap.Config[] RGB_565_IN_CONFIGS =\n      new Bitmap.Config[] {Bitmap.Config.RGB_565};\n  private static final Bitmap.Config[] ARGB_4444_IN_CONFIGS =\n      new Bitmap.Config[] {Bitmap.Config.ARGB_4444};\n  private static final Bitmap.Config[] ALPHA_8_IN_CONFIGS =\n      new Bitmap.Config[] {Bitmap.Config.ALPHA_8};\n\n  private final KeyPool keyPool = new KeyPool();\n  private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();\n  private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();\n\n  @Override\n  public void put(Bitmap bitmap) {\n    int size = Util.getBitmapByteSize(bitmap);\n    Key key = keyPool.get(size, bitmap.getConfig());\n\n    groupedMap.put(key, bitmap);\n\n    NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());\n    Integer current = sizes.get(key.size);\n    sizes.put(key.size, current == null ? 1 : current + 1);\n  }\n\n  @Override\n  @Nullable\n  public Bitmap get(int width, int height, Bitmap.Config config) {\n    int size = Util.getBitmapByteSize(width, height, config);\n    Key bestKey = findBestKey(size, config);\n\n    Bitmap result = groupedMap.get(bestKey);\n    if (result != null) {\n      // Decrement must be called before reconfigure.\n      decrementBitmapOfSize(bestKey.size, result);\n      result.reconfigure(width, height, config);\n    }\n    return result;\n  }\n\n  private Key findBestKey(int size, Bitmap.Config config) {\n    Key result = keyPool.get(size, config);\n    for (Bitmap.Config possibleConfig : getInConfigs(config)) {\n      NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);\n      Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);\n      if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {\n        if (possibleSize != size\n            || (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {\n          keyPool.offer(result);\n          result = keyPool.get(possibleSize, possibleConfig);\n        }\n        break;\n      }\n    }\n    return result;\n  }\n\n  @Override\n  @Nullable\n  public Bitmap removeLast() {\n    Bitmap removed = groupedMap.removeLast();\n    if (removed != null) {\n      int removedSize = Util.getBitmapByteSize(removed);\n      decrementBitmapOfSize(removedSize, removed);\n    }\n    return removed;\n  }\n\n  private void decrementBitmapOfSize(Integer size, Bitmap removed) {\n    Bitmap.Config config = removed.getConfig();\n    NavigableMap<Integer, Integer> sizes = getSizesForConfig(config);\n    Integer current = sizes.get(size);\n    if (current == null) {\n      throw new NullPointerException(\n          \"Tried to decrement empty size\"\n              + \", size: \"\n              + size\n              + \", removed: \"\n              + logBitmap(removed)\n              + \", this: \"\n              + this);\n    }\n\n    if (current == 1) {\n      sizes.remove(size);\n    } else {\n      sizes.put(size, current - 1);\n    }\n  }\n\n  private NavigableMap<Integer, Integer> getSizesForConfig(Bitmap.Config config) {\n    NavigableMap<Integer, Integer> sizes = sortedSizes.get(config);\n    if (sizes == null) {\n      sizes = new TreeMap<>();\n      sortedSizes.put(config, sizes);\n    }\n    return sizes;\n  }\n\n  @Override\n  public String logBitmap(Bitmap bitmap) {\n    int size = Util.getBitmapByteSize(bitmap);\n    return getBitmapString(size, bitmap.getConfig());\n  }\n\n  @Override\n  public String logBitmap(int width, int height, Bitmap.Config config) {\n    int size = Util.getBitmapByteSize(width, height, config);\n    return getBitmapString(size, config);\n  }\n\n  @Override\n  public int getSize(Bitmap bitmap) {\n    return Util.getBitmapByteSize(bitmap);\n  }\n\n  @Override\n  public String toString() {\n    StringBuilder sb =\n        new StringBuilder()\n            .append(\"SizeConfigStrategy{groupedMap=\")\n            .append(groupedMap)\n            .append(\", sortedSizes=(\");\n    for (Map.Entry<Bitmap.Config, NavigableMap<Integer, Integer>> entry : sortedSizes.entrySet()) {\n      sb.append(entry.getKey()).append('[').append(entry.getValue()).append(\"], \");\n    }\n    if (!sortedSizes.isEmpty()) {\n      sb.replace(sb.length() - 2, sb.length(), \"\");\n    }\n    return sb.append(\")}\").toString();\n  }\n\n  @VisibleForTesting\n  static class KeyPool extends BaseKeyPool<Key> {\n\n    public Key get(int size, Bitmap.Config config) {\n      Key result = get();\n      result.init(size, config);\n      return result;\n    }\n\n    @Override\n    protected Key create() {\n      return new Key(this);\n    }\n  }\n\n  @VisibleForTesting\n  static final class Key implements Poolable {\n    private final KeyPool pool;\n\n    @Synthetic int size;\n    private Bitmap.Config config;\n\n    public Key(KeyPool pool) {\n      this.pool = pool;\n    }\n\n    @VisibleForTesting\n    Key(KeyPool pool, int size, Bitmap.Config config) {\n      this(pool);\n      init(size, config);\n    }\n\n    public void init(int size, Bitmap.Config config) {\n      this.size = size;\n      this.config = config;\n    }\n\n    @Override\n    public void offer() {\n      pool.offer(this);\n    }\n\n    @Override\n    public String toString() {\n      return getBitmapString(size, config);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof Key) {\n        Key other = (Key) o;\n        return size == other.size && Util.bothNullOrEqual(config, other.config);\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      int result = size;\n      result = 31 * result + (config != null ? config.hashCode() : 0);\n      return result;\n    }\n  }\n\n  @Synthetic\n  static String getBitmapString(int size, Bitmap.Config config) {\n    return \"[\" + size + \"](\" + config + \")\";\n  }\n\n  private static Bitmap.Config[] getInConfigs(Bitmap.Config requested) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n      if (Bitmap.Config.RGBA_F16.equals(requested)) { // NOPMD - Avoid short circuiting sdk checks.\n        return RGBA_F16_IN_CONFIGS;\n      }\n    }\n\n    switch (requested) {\n      case ARGB_8888:\n        return ARGB_8888_IN_CONFIGS;\n      case RGB_565:\n        return RGB_565_IN_CONFIGS;\n      case ARGB_4444:\n        return ARGB_4444_IN_CONFIGS;\n      case ALPHA_8:\n        return ALPHA_8_IN_CONFIGS;\n      default:\n        return new Bitmap.Config[] {requested};\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategy.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.util.NavigableMap;\n\n/**\n * A strategy for reusing bitmaps that relies on {@link Bitmap#reconfigure(int, int,\n * Bitmap.Config)}.\n *\n * <p>Requires {@link Build.VERSION_CODES#KITKAT KitKat} or higher.\n */\n@RequiresApi(Build.VERSION_CODES.KITKAT)\nfinal class SizeStrategy implements LruPoolStrategy {\n  private static final int MAX_SIZE_MULTIPLE = 8;\n  private final KeyPool keyPool = new KeyPool();\n  private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();\n  private final NavigableMap<Integer, Integer> sortedSizes = new PrettyPrintTreeMap<>();\n\n  @Override\n  public void put(Bitmap bitmap) {\n    int size = Util.getBitmapByteSize(bitmap);\n    final Key key = keyPool.get(size);\n\n    groupedMap.put(key, bitmap);\n\n    Integer current = sortedSizes.get(key.size);\n    sortedSizes.put(key.size, current == null ? 1 : current + 1);\n  }\n\n  @Override\n  @Nullable\n  public Bitmap get(int width, int height, Bitmap.Config config) {\n    final int size = Util.getBitmapByteSize(width, height, config);\n    Key key = keyPool.get(size);\n\n    Integer possibleSize = sortedSizes.ceilingKey(size);\n    if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) {\n      keyPool.offer(key);\n      key = keyPool.get(possibleSize);\n    }\n\n    // Do a get even if we know we don't have a bitmap so that the key moves to the front in the\n    // lru pool\n    final Bitmap result = groupedMap.get(key);\n    if (result != null) {\n      result.reconfigure(width, height, config);\n      decrementBitmapOfSize(possibleSize);\n    }\n\n    return result;\n  }\n\n  @Override\n  @Nullable\n  public Bitmap removeLast() {\n    Bitmap removed = groupedMap.removeLast();\n    if (removed != null) {\n      final int removedSize = Util.getBitmapByteSize(removed);\n      decrementBitmapOfSize(removedSize);\n    }\n    return removed;\n  }\n\n  private void decrementBitmapOfSize(Integer size) {\n    Integer current = sortedSizes.get(size);\n    if (current == 1) {\n      sortedSizes.remove(size);\n    } else {\n      sortedSizes.put(size, current - 1);\n    }\n  }\n\n  @Override\n  public String logBitmap(Bitmap bitmap) {\n    return getBitmapString(bitmap);\n  }\n\n  @Override\n  public String logBitmap(int width, int height, Bitmap.Config config) {\n    int size = Util.getBitmapByteSize(width, height, config);\n    return getBitmapString(size);\n  }\n\n  @Override\n  public int getSize(Bitmap bitmap) {\n    return Util.getBitmapByteSize(bitmap);\n  }\n\n  @Override\n  public String toString() {\n    return \"SizeStrategy:\\n  \" + groupedMap + \"\\n\" + \"  SortedSizes\" + sortedSizes;\n  }\n\n  private static String getBitmapString(Bitmap bitmap) {\n    int size = Util.getBitmapByteSize(bitmap);\n    return getBitmapString(size);\n  }\n\n  @Synthetic\n  static String getBitmapString(int size) {\n    return \"[\" + size + \"]\";\n  }\n\n  // Non-final for mocking.\n  @VisibleForTesting\n  static class KeyPool extends BaseKeyPool<Key> {\n\n    public Key get(int size) {\n      Key result = super.get();\n      result.init(size);\n      return result;\n    }\n\n    @Override\n    protected Key create() {\n      return new Key(this);\n    }\n  }\n\n  @VisibleForTesting\n  static final class Key implements Poolable {\n    private final KeyPool pool;\n    @Synthetic int size;\n\n    Key(KeyPool pool) {\n      this.pool = pool;\n    }\n\n    public void init(int size) {\n      this.size = size;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof Key) {\n        Key other = (Key) o;\n        return size == other.size;\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      return size;\n    }\n\n    // PMD.AccessorMethodGeneration: https://github.com/pmd/pmd/issues/807\n    @SuppressWarnings(\"PMD.AccessorMethodGeneration\")\n    @Override\n    public String toString() {\n      return getBitmapString(size);\n    }\n\n    @Override\n    public void offer() {\n      pool.offer(this);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCache.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Key;\nimport java.io.File;\n\n/** An interface for writing to and reading from a disk cache. */\npublic interface DiskCache {\n\n  /** An interface for lazily creating a disk cache. */\n  interface Factory {\n    /** 250 MB of cache. */\n    int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;\n\n    String DEFAULT_DISK_CACHE_DIR = \"image_manager_disk_cache\";\n\n    /** Returns a new disk cache, or {@code null} if no disk cache could be created. */\n    @Nullable\n    DiskCache build();\n  }\n\n  /** An interface to actually write data to a key in the disk cache. */\n  interface Writer {\n    /**\n     * Writes data to the file and returns true if the write was successful and should be committed,\n     * and false if the write should be aborted.\n     *\n     * @param file The File the Writer should write to.\n     */\n    boolean write(@NonNull File file);\n  }\n\n  /**\n   * Get the cache for the value at the given key.\n   *\n   * <p>Note - This is potentially dangerous, someone may write a new value to the file at any point\n   * in time and we won't know about it.\n   *\n   * @param key The key in the cache.\n   * @return An InputStream representing the data at key at the time get is called.\n   */\n  @Nullable\n  File get(Key key);\n\n  /**\n   * Write to a key in the cache. {@link Writer} is used so that the cache implementation can\n   * perform actions after the write finishes, like commit (via atomic file rename).\n   *\n   * @param key The key to write to.\n   * @param writer An interface that will write data given an OutputStream for the key.\n   */\n  void put(Key key, Writer writer);\n\n  /**\n   * Remove the key and value from the cache.\n   *\n   * @param key The key to remove.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  void delete(Key key);\n\n  /** Clear the cache. */\n  void clear();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCacheAdapter.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport com.bumptech.glide.load.Key;\nimport java.io.File;\n\n/** A simple class that returns null for all gets and ignores all writes. */\npublic class DiskCacheAdapter implements DiskCache {\n  @Override\n  public File get(Key key) {\n    // no op, default for overriders\n    return null;\n  }\n\n  @Override\n  public void put(Key key, Writer writer) {\n    // no op, default for overriders\n  }\n\n  @Override\n  public void delete(Key key) {\n    // no op, default for overriders\n  }\n\n  @Override\n  public void clear() {\n    // no op, default for overriders\n  }\n\n  /** Default factory for {@link DiskCacheAdapter}. */\n  public static final class Factory implements DiskCache.Factory {\n    @Override\n    public DiskCache build() {\n      return new DiskCacheAdapter();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCacheWriteLocker.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.ArrayDeque;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * Keeps a map of keys to locks that allows locks to be removed from the map when no longer in use\n * so the size of the collection is bounded.\n *\n * <p>This class will be accessed by multiple threads in a thread pool and ensures that the number\n * of threads interested in each lock is updated atomically so that when the count reaches 0, the\n * lock can safely be removed from the map.\n */\nfinal class DiskCacheWriteLocker {\n  private final Map<String, WriteLock> locks = new HashMap<>();\n  private final WriteLockPool writeLockPool = new WriteLockPool();\n\n  void acquire(String safeKey) {\n    WriteLock writeLock;\n    synchronized (this) {\n      writeLock = locks.get(safeKey);\n      if (writeLock == null) {\n        writeLock = writeLockPool.obtain();\n        locks.put(safeKey, writeLock);\n      }\n      writeLock.interestedThreads++;\n    }\n\n    writeLock.lock.lock();\n  }\n\n  void release(String safeKey) {\n    WriteLock writeLock;\n    synchronized (this) {\n      writeLock = Preconditions.checkNotNull(locks.get(safeKey));\n      if (writeLock.interestedThreads < 1) {\n        throw new IllegalStateException(\n            \"Cannot release a lock that is not held\"\n                + \", safeKey: \"\n                + safeKey\n                + \", interestedThreads: \"\n                + writeLock.interestedThreads);\n      }\n\n      writeLock.interestedThreads--;\n      if (writeLock.interestedThreads == 0) {\n        WriteLock removed = locks.remove(safeKey);\n        if (!removed.equals(writeLock)) {\n          throw new IllegalStateException(\n              \"Removed the wrong lock\"\n                  + \", expected to remove: \"\n                  + writeLock\n                  + \", but actually removed: \"\n                  + removed\n                  + \", safeKey: \"\n                  + safeKey);\n        }\n        writeLockPool.offer(removed);\n      }\n    }\n\n    writeLock.lock.unlock();\n  }\n\n  private static class WriteLock {\n    final Lock lock = new ReentrantLock();\n    int interestedThreads;\n\n    @Synthetic\n    WriteLock() {}\n  }\n\n  private static class WriteLockPool {\n    private static final int MAX_POOL_SIZE = 10;\n    private final Queue<WriteLock> pool = new ArrayDeque<>();\n\n    @Synthetic\n    WriteLockPool() {}\n\n    WriteLock obtain() {\n      WriteLock result;\n      synchronized (pool) {\n        result = pool.poll();\n      }\n      if (result == null) {\n        result = new WriteLock();\n      }\n      return result;\n    }\n\n    void offer(WriteLock writeLock) {\n      synchronized (pool) {\n        if (pool.size() < MAX_POOL_SIZE) {\n          pool.offer(writeLock);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/DiskLruCacheFactory.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport java.io.File;\n\n/**\n * Creates an {@link com.bumptech.glide.disklrucache.DiskLruCache} based disk cache in the specified\n * disk cache directory.\n *\n * <p>If you need to make I/O access before returning the cache directory use the {@link\n * DiskLruCacheFactory#DiskLruCacheFactory(CacheDirectoryGetter, long)} constructor variant.\n */\n// Public API.\n@SuppressWarnings(\"unused\")\npublic class DiskLruCacheFactory implements DiskCache.Factory {\n  private final long diskCacheSize;\n  private final CacheDirectoryGetter cacheDirectoryGetter;\n\n  /** Interface called out of UI thread to get the cache folder. */\n  public interface CacheDirectoryGetter {\n    File getCacheDirectory();\n  }\n\n  public DiskLruCacheFactory(final String diskCacheFolder, long diskCacheSize) {\n    this(\n        new CacheDirectoryGetter() {\n          @Override\n          public File getCacheDirectory() {\n            return new File(diskCacheFolder);\n          }\n        },\n        diskCacheSize);\n  }\n\n  public DiskLruCacheFactory(\n      final String diskCacheFolder, final String diskCacheName, long diskCacheSize) {\n    this(\n        new CacheDirectoryGetter() {\n          @Override\n          public File getCacheDirectory() {\n            return new File(diskCacheFolder, diskCacheName);\n          }\n        },\n        diskCacheSize);\n  }\n\n  /**\n   * When using this constructor {@link CacheDirectoryGetter#getCacheDirectory()} will be called out\n   * of UI thread, allowing to do I/O access without performance impacts.\n   *\n   * @param cacheDirectoryGetter Interface called out of UI thread to get the cache folder.\n   * @param diskCacheSize Desired max bytes size for the LRU disk cache.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public DiskLruCacheFactory(CacheDirectoryGetter cacheDirectoryGetter, long diskCacheSize) {\n    this.diskCacheSize = diskCacheSize;\n    this.cacheDirectoryGetter = cacheDirectoryGetter;\n  }\n\n  @Override\n  public DiskCache build() {\n    File cacheDir = cacheDirectoryGetter.getCacheDirectory();\n\n    if (cacheDir == null) {\n      return null;\n    }\n\n    if (cacheDir.isDirectory() || cacheDir.mkdirs()) {\n      return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);\n    }\n\n    return null;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/DiskLruCacheWrapper.java",
    "content": "/*\n * Copyright (c) 2013. Bump Technologies Inc. All Rights Reserved.\n */\n\npackage com.bumptech.glide.load.engine.cache;\n\nimport android.util.Log;\nimport com.bumptech.glide.disklrucache.DiskLruCache;\nimport com.bumptech.glide.disklrucache.DiskLruCache.Value;\nimport com.bumptech.glide.load.Key;\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * The default DiskCache implementation. There must be no more than one active instance for a given\n * directory at a time.\n *\n * @see #get(java.io.File, long)\n */\npublic class DiskLruCacheWrapper implements DiskCache {\n  private static final String TAG = \"DiskLruCacheWrapper\";\n\n  private static final int APP_VERSION = 1;\n  private static final int VALUE_COUNT = 1;\n  private static DiskLruCacheWrapper wrapper;\n\n  private final SafeKeyGenerator safeKeyGenerator;\n  private final File directory;\n  private final long maxSize;\n  private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();\n  private DiskLruCache diskLruCache;\n\n  /**\n   * Get a DiskCache in the given directory and size. If a disk cache has already been created with\n   * a different directory and/or size, it will be returned instead and the new arguments will be\n   * ignored.\n   *\n   * @param directory The directory for the disk cache\n   * @param maxSize The max size for the disk cache\n   * @return The new disk cache with the given arguments, or the current cache if one already exists\n   * @deprecated Use {@link #create(File, long)} to create a new cache with the specified arguments.\n   */\n  @SuppressWarnings(\"deprecation\")\n  @Deprecated\n  public static synchronized DiskCache get(File directory, long maxSize) {\n    // TODO calling twice with different arguments makes it return the cache for the same\n    // directory, it's public!\n    if (wrapper == null) {\n      wrapper = new DiskLruCacheWrapper(directory, maxSize);\n    }\n    return wrapper;\n  }\n\n  /**\n   * Create a new DiskCache in the given directory with a specified max size.\n   *\n   * @param directory The directory for the disk cache\n   * @param maxSize The max size for the disk cache\n   * @return The new disk cache with the given arguments\n   */\n  @SuppressWarnings(\"deprecation\")\n  public static DiskCache create(File directory, long maxSize) {\n    return new DiskLruCacheWrapper(directory, maxSize);\n  }\n\n  /**\n   * @deprecated Do not extend this class.\n   */\n  @Deprecated\n  // Deprecated public API.\n  @SuppressWarnings({\"WeakerAccess\", \"DeprecatedIsStillUsed\"})\n  protected DiskLruCacheWrapper(File directory, long maxSize) {\n    this.directory = directory;\n    this.maxSize = maxSize;\n    this.safeKeyGenerator = new SafeKeyGenerator();\n  }\n\n  private synchronized DiskLruCache getDiskCache() throws IOException {\n    if (diskLruCache == null) {\n      diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);\n    }\n    return diskLruCache;\n  }\n\n  @Override\n  public File get(Key key) {\n    String safeKey = safeKeyGenerator.getSafeKey(key);\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(TAG, \"Get: Obtained: \" + safeKey + \" for for Key: \" + key);\n    }\n    File result = null;\n    try {\n      // It is possible that the there will be a put in between these two gets. If so that shouldn't\n      // be a problem because we will always put the same value at the same key so our input streams\n      // will still represent the same data.\n      final DiskLruCache.Value value = getDiskCache().get(safeKey);\n      if (value != null) {\n        result = value.getFile(0);\n      }\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Unable to get from disk cache\", e);\n      }\n    }\n    return result;\n  }\n\n  @Override\n  public void put(Key key, Writer writer) {\n    // We want to make sure that puts block so that data is available when put completes. We may\n    // actually not write any data if we find that data is written by the time we acquire the lock.\n    String safeKey = safeKeyGenerator.getSafeKey(key);\n    writeLocker.acquire(safeKey);\n    try {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Put: Obtained: \" + safeKey + \" for for Key: \" + key);\n      }\n      try {\n        // We assume we only need to put once, so if data was written while we were trying to get\n        // the lock, we can simply abort.\n        DiskLruCache diskCache = getDiskCache();\n        Value current = diskCache.get(safeKey);\n        if (current != null) {\n          return;\n        }\n\n        DiskLruCache.Editor editor = diskCache.edit(safeKey);\n        if (editor == null) {\n          throw new IllegalStateException(\"Had two simultaneous puts for: \" + safeKey);\n        }\n        try {\n          File file = editor.getFile(0);\n          if (writer.write(file)) {\n            editor.commit();\n          }\n        } finally {\n          editor.abortUnlessCommitted();\n        }\n      } catch (IOException e) {\n        if (Log.isLoggable(TAG, Log.WARN)) {\n          Log.w(TAG, \"Unable to put to disk cache\", e);\n        }\n      }\n    } finally {\n      writeLocker.release(safeKey);\n    }\n  }\n\n  @Override\n  public void delete(Key key) {\n    String safeKey = safeKeyGenerator.getSafeKey(key);\n    try {\n      getDiskCache().remove(safeKey);\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Unable to delete from disk cache\", e);\n      }\n    }\n  }\n\n  @Override\n  public synchronized void clear() {\n    try {\n      getDiskCache().delete();\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Unable to clear disk cache or disk cache cleared externally\", e);\n      }\n    } finally {\n      // Delete can close the cache but still throw. If we don't null out the disk cache here, every\n      // subsequent request will try to act on a closed disk cache and fail. By nulling out the disk\n      // cache we at least allow for attempts to open the cache in the future. See #2465.\n      resetDiskCache();\n    }\n  }\n\n  private synchronized void resetDiskCache() {\n    diskLruCache = null;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/ExternalCacheDiskCacheFactory.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport android.content.Context;\nimport java.io.File;\n\n/**\n * Creates an {@link com.bumptech.glide.disklrucache.DiskLruCache} based disk cache in the external\n * disk cache directory.\n *\n * <p><b>Images can be read by everyone when using external disk cache.</b>\n *\n * @deprecated use {@link ExternalPreferredCacheDiskCacheFactory} instead.\n */\n// Public API.\n@SuppressWarnings({\"unused\", \"WeakerAccess\"})\n@Deprecated\npublic final class ExternalCacheDiskCacheFactory extends DiskLruCacheFactory {\n\n  public ExternalCacheDiskCacheFactory(Context context) {\n    this(\n        context,\n        DiskCache.Factory.DEFAULT_DISK_CACHE_DIR,\n        DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);\n  }\n\n  public ExternalCacheDiskCacheFactory(Context context, int diskCacheSize) {\n    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);\n  }\n\n  public ExternalCacheDiskCacheFactory(\n      final Context context, final String diskCacheName, int diskCacheSize) {\n    super(\n        new CacheDirectoryGetter() {\n          @Override\n          public File getCacheDirectory() {\n            File cacheDirectory = context.getExternalCacheDir();\n            if (cacheDirectory == null) {\n              return null;\n            }\n            if (diskCacheName != null) {\n              return new File(cacheDirectory, diskCacheName);\n            }\n            return cacheDirectory;\n          }\n        },\n        diskCacheSize);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/ExternalPreferredCacheDiskCacheFactory.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport android.content.Context;\nimport androidx.annotation.Nullable;\nimport java.io.File;\n\n/**\n * Creates an {@link com.bumptech.glide.disklrucache.DiskLruCache} based disk cache in the external\n * disk cache directory, which falls back to the internal disk cache if no external storage is\n * available. If ever fell back to the internal disk cache, will use that one from that moment on.\n *\n * <p><b>Images can be read by everyone when using external disk cache.</b>\n */\n// Public API.\n@SuppressWarnings({\"unused\", \"WeakerAccess\"})\npublic final class ExternalPreferredCacheDiskCacheFactory extends DiskLruCacheFactory {\n\n  public ExternalPreferredCacheDiskCacheFactory(Context context) {\n    this(\n        context,\n        DiskCache.Factory.DEFAULT_DISK_CACHE_DIR,\n        DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);\n  }\n\n  public ExternalPreferredCacheDiskCacheFactory(Context context, long diskCacheSize) {\n    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);\n  }\n\n  public ExternalPreferredCacheDiskCacheFactory(\n      final Context context, final String diskCacheName, final long diskCacheSize) {\n    super(\n        new CacheDirectoryGetter() {\n          @Nullable\n          private File getInternalCacheDirectory() {\n            File cacheDirectory = context.getCacheDir();\n            if (cacheDirectory == null) {\n              return null;\n            }\n            if (diskCacheName != null) {\n              return new File(cacheDirectory, diskCacheName);\n            }\n            return cacheDirectory;\n          }\n\n          @Override\n          public File getCacheDirectory() {\n            File internalCacheDirectory = getInternalCacheDirectory();\n\n            // Already used internal cache, so keep using that one,\n            // thus avoiding using both external and internal with transient errors.\n            if (internalCacheDirectory != null && internalCacheDirectory.exists()) {\n              return internalCacheDirectory;\n            }\n\n            File cacheDirectory = context.getExternalCacheDir();\n\n            // Shared storage is not available.\n            if (cacheDirectory == null || !cacheDirectory.canWrite()) {\n              return internalCacheDirectory;\n            }\n            if (diskCacheName != null) {\n              return new File(cacheDirectory, diskCacheName);\n            }\n            return cacheDirectory;\n          }\n        },\n        diskCacheSize);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/InternalCacheDiskCacheFactory.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport android.content.Context;\nimport java.io.File;\n\n/**\n * Creates an {@link com.bumptech.glide.disklrucache.DiskLruCache} based disk cache in the internal\n * disk cache directory.\n */\n// Public API.\n@SuppressWarnings({\"WeakerAccess\", \"unused\"})\npublic final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {\n\n  public InternalCacheDiskCacheFactory(Context context) {\n    this(\n        context,\n        DiskCache.Factory.DEFAULT_DISK_CACHE_DIR,\n        DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);\n  }\n\n  public InternalCacheDiskCacheFactory(Context context, long diskCacheSize) {\n    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);\n  }\n\n  public InternalCacheDiskCacheFactory(\n      final Context context, final String diskCacheName, long diskCacheSize) {\n    super(\n        new CacheDirectoryGetter() {\n          @Override\n          public File getCacheDirectory() {\n            File cacheDirectory = context.getCacheDir();\n            if (cacheDirectory == null) {\n              return null;\n            }\n            if (diskCacheName != null) {\n              return new File(cacheDirectory, diskCacheName);\n            }\n            return cacheDirectory;\n          }\n        },\n        diskCacheSize);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/LruResourceCache.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport android.annotation.SuppressLint;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.LruCache;\n\n/** An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s. */\npublic class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {\n  private ResourceRemovedListener listener;\n\n  /**\n   * Constructor for LruResourceCache.\n   *\n   * @param size The maximum size in bytes the in memory cache can use.\n   */\n  public LruResourceCache(long size) {\n    super(size);\n  }\n\n  @Override\n  public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) {\n    this.listener = listener;\n  }\n\n  @Override\n  protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {\n    if (listener != null && item != null) {\n      listener.onResourceRemoved(item);\n    }\n  }\n\n  @Override\n  protected int getSize(@Nullable Resource<?> item) {\n    if (item == null) {\n      return super.getSize(null);\n    } else {\n      return item.getSize();\n    }\n  }\n\n  @SuppressLint(\"InlinedApi\")\n  @Override\n  public void trimMemory(int level) {\n    if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {\n      // Entering list of cached background apps\n      // Evict our entire bitmap cache\n      clearMemory();\n    } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN\n        || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {\n      // The app's UI is no longer visible, or app is in the foreground but system is running\n      // critically low on memory\n      // Evict oldest half of our bitmap cache\n      trimToSize(getMaxSize() / 2);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCache.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.Resource;\n\n/** An interface for adding and removing resources from an in memory cache. */\npublic interface MemoryCache {\n  /** An interface that will be called whenever a bitmap is removed from the cache. */\n  interface ResourceRemovedListener {\n    void onResourceRemoved(@NonNull Resource<?> removed);\n  }\n\n  /** Returns the sum of the sizes of all the contents of the cache in bytes. */\n  long getCurrentSize();\n\n  /** Returns the current maximum size in bytes of the cache. */\n  long getMaxSize();\n\n  /**\n   * Adjust the maximum size of the cache by multiplying the original size of the cache by the given\n   * multiplier.\n   *\n   * <p>If the size multiplier causes the size of the cache to be decreased, items will be evicted\n   * until the cache is smaller than the new size.\n   *\n   * @param multiplier A size multiplier {@code >= 0}.\n   */\n  void setSizeMultiplier(float multiplier);\n\n  /**\n   * Removes the value for the given key and returns it if present or null otherwise.\n   *\n   * @param key The key.\n   */\n  @Nullable\n  Resource<?> remove(@NonNull Key key);\n\n  /**\n   * Add bitmap to the cache with the given key.\n   *\n   * @param key The key to retrieve the bitmap.\n   * @param resource The {@link com.bumptech.glide.load.engine.EngineResource} to store.\n   * @return The old value of key (null if key is not in map).\n   */\n  @Nullable\n  Resource<?> put(@NonNull Key key, @Nullable Resource<?> resource);\n\n  /**\n   * Set the listener to be called when a bitmap is removed from the cache.\n   *\n   * @param listener The listener.\n   */\n  void setResourceRemovedListener(@NonNull ResourceRemovedListener listener);\n\n  /** Evict all items from the memory cache. */\n  void clearMemory();\n\n  /**\n   * Trim the memory cache to the appropriate level. Typically called on the callback onTrimMemory.\n   *\n   * @param level This integer represents a trim level as specified in {@link\n   *     android.content.ComponentCallbacks2}.\n   */\n  void trimMemory(int level);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCacheAdapter.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.Resource;\n\n/** A simple class that ignores all puts and returns null for all gets. */\npublic class MemoryCacheAdapter implements MemoryCache {\n\n  private ResourceRemovedListener listener;\n\n  @Override\n  public long getCurrentSize() {\n    return 0;\n  }\n\n  @Override\n  public long getMaxSize() {\n    return 0;\n  }\n\n  @Override\n  public void setSizeMultiplier(float multiplier) {\n    // Do nothing.\n  }\n\n  @Nullable\n  @Override\n  public Resource<?> remove(@NonNull Key key) {\n    return null;\n  }\n\n  @Nullable\n  @Override\n  public Resource<?> put(@NonNull Key key, @Nullable Resource<?> resource) {\n    if (resource != null) {\n      listener.onResourceRemoved(resource);\n    }\n    return null;\n  }\n\n  @Override\n  public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) {\n    this.listener = listener;\n  }\n\n  @Override\n  public void clearMemory() {\n    // Do nothing.\n  }\n\n  @Override\n  public void trimMemory(int level) {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/MemorySizeCalculator.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport android.annotation.TargetApi;\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.os.Build;\nimport android.text.format.Formatter;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\n\n/**\n * A calculator that tries to intelligently determine cache sizes for a given device based on some\n * constants and the devices screen density, width, and height.\n */\npublic final class MemorySizeCalculator {\n  private static final String TAG = \"MemorySizeCalculator\";\n  @VisibleForTesting static final int BYTES_PER_ARGB_8888_PIXEL = 4;\n  private static final int LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR = 2;\n\n  private final int bitmapPoolSize;\n  private final int memoryCacheSize;\n  private final Context context;\n  private final int arrayPoolSize;\n\n  interface ScreenDimensions {\n    int getWidthPixels();\n\n    int getHeightPixels();\n  }\n\n  // Package private to avoid PMD warning.\n  MemorySizeCalculator(MemorySizeCalculator.Builder builder) {\n    this.context = builder.context;\n\n    arrayPoolSize =\n        isLowMemoryDevice(builder.activityManager)\n            ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR\n            : builder.arrayPoolSizeBytes;\n    int maxSize =\n        getMaxSize(\n            builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);\n\n    int widthPixels = builder.screenDimensions.getWidthPixels();\n    int heightPixels = builder.screenDimensions.getHeightPixels();\n    int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;\n\n    int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);\n\n    int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);\n    int availableSize = maxSize - arrayPoolSize;\n\n    if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {\n      memoryCacheSize = targetMemoryCacheSize;\n      bitmapPoolSize = targetBitmapPoolSize;\n    } else {\n      float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);\n      memoryCacheSize = Math.round(part * builder.memoryCacheScreens);\n      bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);\n    }\n\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      Log.d(\n          TAG,\n          \"Calculation complete\"\n              + \", Calculated memory cache size: \"\n              + toMb(memoryCacheSize)\n              + \", pool size: \"\n              + toMb(bitmapPoolSize)\n              + \", byte array size: \"\n              + toMb(arrayPoolSize)\n              + \", memory class limited? \"\n              + (targetMemoryCacheSize + targetBitmapPoolSize > maxSize)\n              + \", max size: \"\n              + toMb(maxSize)\n              + \", memoryClass: \"\n              + builder.activityManager.getMemoryClass()\n              + \", isLowMemoryDevice: \"\n              + isLowMemoryDevice(builder.activityManager));\n    }\n  }\n\n  /** Returns the recommended memory cache size for the device it is run on in bytes. */\n  public int getMemoryCacheSize() {\n    return memoryCacheSize;\n  }\n\n  /** Returns the recommended bitmap pool size for the device it is run on in bytes. */\n  public int getBitmapPoolSize() {\n    return bitmapPoolSize;\n  }\n\n  /** Returns the recommended array pool size for the device it is run on in bytes. */\n  public int getArrayPoolSizeInBytes() {\n    return arrayPoolSize;\n  }\n\n  private static int getMaxSize(\n      ActivityManager activityManager, float maxSizeMultiplier, float lowMemoryMaxSizeMultiplier) {\n    final int memoryClassBytes = activityManager.getMemoryClass() * 1024 * 1024;\n    final boolean isLowMemoryDevice = isLowMemoryDevice(activityManager);\n    return Math.round(\n        memoryClassBytes * (isLowMemoryDevice ? lowMemoryMaxSizeMultiplier : maxSizeMultiplier));\n  }\n\n  private String toMb(int bytes) {\n    return Formatter.formatFileSize(context, bytes);\n  }\n\n  @TargetApi(Build.VERSION_CODES.KITKAT)\n  @Synthetic\n  static boolean isLowMemoryDevice(ActivityManager activityManager) {\n    // Explicitly check with an if statement, on some devices both parts of boolean expressions\n    // can be evaluated even if we'd normally expect a short circuit.\n    //noinspection SimplifiableIfStatement\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n      return activityManager.isLowRamDevice();\n    } else {\n      return true;\n    }\n  }\n\n  /**\n   * Constructs an {@link MemorySizeCalculator} with reasonable defaults that can be optionally\n   * overridden.\n   */\n  // Public API.\n  @SuppressWarnings({\"WeakerAccess\", \"unused\"})\n  public static final class Builder {\n    @VisibleForTesting static final int MEMORY_CACHE_TARGET_SCREENS = 2;\n\n    /**\n     * On Android O+, we use {@link android.graphics.Bitmap.Config#HARDWARE} for all reasonably\n     * sized images unless we're creating thumbnails for the first time. As a result, the Bitmap\n     * pool is much less important on O than it was on previous versions.\n     */\n    static final int BITMAP_POOL_TARGET_SCREENS =\n        Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4 : 1;\n\n    static final float MAX_SIZE_MULTIPLIER = 0.4f;\n    static final float LOW_MEMORY_MAX_SIZE_MULTIPLIER = 0.33f;\n    // 4MB.\n    static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;\n\n    @Synthetic final Context context;\n\n    // Modifiable (non-final) for testing.\n    @Synthetic ActivityManager activityManager;\n    @Synthetic ScreenDimensions screenDimensions;\n\n    @Synthetic float memoryCacheScreens = MEMORY_CACHE_TARGET_SCREENS;\n    @Synthetic float bitmapPoolScreens = BITMAP_POOL_TARGET_SCREENS;\n    @Synthetic float maxSizeMultiplier = MAX_SIZE_MULTIPLIER;\n    @Synthetic float lowMemoryMaxSizeMultiplier = LOW_MEMORY_MAX_SIZE_MULTIPLIER;\n    @Synthetic int arrayPoolSizeBytes = ARRAY_POOL_SIZE_BYTES;\n\n    public Builder(Context context) {\n      this.context = context;\n      activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n      screenDimensions =\n          new DisplayMetricsScreenDimensions(context.getResources().getDisplayMetrics());\n\n      // On Android O+ Bitmaps are allocated natively, ART is much more efficient at managing\n      // garbage and we rely heavily on HARDWARE Bitmaps, making Bitmap re-use much less important.\n      // We prefer to preserve RAM on these devices and take the small performance hit of not\n      // re-using Bitmaps and textures when loading very small images or generating thumbnails.\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isLowMemoryDevice(activityManager)) {\n        bitmapPoolScreens = 0;\n      }\n    }\n\n    /**\n     * Sets the number of device screens worth of pixels the {@link\n     * com.bumptech.glide.load.engine.cache.MemoryCache} should be able to hold and returns this\n     * Builder.\n     */\n    public Builder setMemoryCacheScreens(float memoryCacheScreens) {\n      Preconditions.checkArgument(\n          memoryCacheScreens >= 0, \"Memory cache screens must be greater than or equal to 0\");\n      this.memoryCacheScreens = memoryCacheScreens;\n      return this;\n    }\n\n    /**\n     * Sets the number of device screens worth of pixels the {@link\n     * com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} should be able to hold and returns\n     * this Builder.\n     */\n    public Builder setBitmapPoolScreens(float bitmapPoolScreens) {\n      Preconditions.checkArgument(\n          bitmapPoolScreens >= 0, \"Bitmap pool screens must be greater than or equal to 0\");\n      this.bitmapPoolScreens = bitmapPoolScreens;\n      return this;\n    }\n\n    /**\n     * Sets the maximum percentage of the device's memory class for standard devices that can be\n     * taken up by Glide's {@link com.bumptech.glide.load.engine.cache.MemoryCache} and {@link\n     * com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} put together, and returns this\n     * builder.\n     */\n    public Builder setMaxSizeMultiplier(float maxSizeMultiplier) {\n      Preconditions.checkArgument(\n          maxSizeMultiplier >= 0 && maxSizeMultiplier <= 1,\n          \"Size multiplier must be between 0 and 1\");\n      this.maxSizeMultiplier = maxSizeMultiplier;\n      return this;\n    }\n\n    /**\n     * Sets the maximum percentage of the device's memory class for low ram devices that can be\n     * taken up by Glide's {@link com.bumptech.glide.load.engine.cache.MemoryCache} and {@link\n     * com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} put together, and returns this\n     * builder.\n     *\n     * @see ActivityManager#isLowRamDevice()\n     */\n    public Builder setLowMemoryMaxSizeMultiplier(float lowMemoryMaxSizeMultiplier) {\n      Preconditions.checkArgument(\n          lowMemoryMaxSizeMultiplier >= 0 && lowMemoryMaxSizeMultiplier <= 1,\n          \"Low memory max size multiplier must be between 0 and 1\");\n      this.lowMemoryMaxSizeMultiplier = lowMemoryMaxSizeMultiplier;\n      return this;\n    }\n\n    /**\n     * Sets the size in bytes of the {@link com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool}\n     * to use to store temporary arrays while decoding data and returns this builder.\n     *\n     * <p>This number will be halved on low memory devices that return {@code true} from {@link\n     * ActivityManager#isLowRamDevice()}.\n     */\n    public Builder setArrayPoolSize(int arrayPoolSizeBytes) {\n      this.arrayPoolSizeBytes = arrayPoolSizeBytes;\n      return this;\n    }\n\n    @VisibleForTesting\n    Builder setActivityManager(ActivityManager activityManager) {\n      this.activityManager = activityManager;\n      return this;\n    }\n\n    @VisibleForTesting\n    Builder setScreenDimensions(ScreenDimensions screenDimensions) {\n      this.screenDimensions = screenDimensions;\n      return this;\n    }\n\n    public MemorySizeCalculator build() {\n      return new MemorySizeCalculator(this);\n    }\n  }\n\n  private static final class DisplayMetricsScreenDimensions implements ScreenDimensions {\n    private final DisplayMetrics displayMetrics;\n\n    DisplayMetricsScreenDimensions(DisplayMetrics displayMetrics) {\n      this.displayMetrics = displayMetrics;\n    }\n\n    @Override\n    public int getWidthPixels() {\n      return displayMetrics.widthPixels;\n    }\n\n    @Override\n    public int getHeightPixels() {\n      return displayMetrics.heightPixels;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/cache/SafeKeyGenerator.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport androidx.annotation.NonNull;\nimport androidx.core.util.Pools;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.util.LruCache;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport com.bumptech.glide.util.pool.FactoryPools;\nimport com.bumptech.glide.util.pool.StateVerifier;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n/**\n * A class that generates and caches safe and unique string file names from {@link\n * com.bumptech.glide.load.Key}s.\n */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic class SafeKeyGenerator {\n  private final LruCache<Key, String> loadIdToSafeHash = new LruCache<>(1000);\n  private final Pools.Pool<PoolableDigestContainer> digestPool =\n      FactoryPools.threadSafe(\n          10,\n          new FactoryPools.Factory<PoolableDigestContainer>() {\n            @Override\n            public PoolableDigestContainer create() {\n              try {\n                return new PoolableDigestContainer(MessageDigest.getInstance(\"SHA-256\"));\n              } catch (NoSuchAlgorithmException e) {\n                throw new RuntimeException(e);\n              }\n            }\n          });\n\n  public String getSafeKey(Key key) {\n    String safeKey;\n    synchronized (loadIdToSafeHash) {\n      safeKey = loadIdToSafeHash.get(key);\n    }\n    if (safeKey == null) {\n      safeKey = calculateHexStringDigest(key);\n    }\n    synchronized (loadIdToSafeHash) {\n      loadIdToSafeHash.put(key, safeKey);\n    }\n    return safeKey;\n  }\n\n  private String calculateHexStringDigest(Key key) {\n    PoolableDigestContainer container = Preconditions.checkNotNull(digestPool.acquire());\n    try {\n      key.updateDiskCacheKey(container.messageDigest);\n      // calling digest() will automatically reset()\n      return Util.sha256BytesToHex(container.messageDigest.digest());\n    } finally {\n      digestPool.release(container);\n    }\n  }\n\n  private static final class PoolableDigestContainer implements FactoryPools.Poolable {\n\n    @Synthetic final MessageDigest messageDigest;\n    private final StateVerifier stateVerifier = StateVerifier.newInstance();\n\n    PoolableDigestContainer(MessageDigest messageDigest) {\n      this.messageDigest = messageDigest;\n    }\n\n    @NonNull\n    @Override\n    public StateVerifier getVerifier() {\n      return stateVerifier;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/executor/GlideExecutor.java",
    "content": "package com.bumptech.glide.load.engine.executor;\n\nimport android.os.StrictMode;\nimport android.os.StrictMode.ThreadPolicy;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.PriorityBlockingQueue;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Function;\n\n/** A prioritized {@link ThreadPoolExecutor} for running jobs in Glide. */\npublic final class GlideExecutor implements ExecutorService {\n  /**\n   * The default thread name prefix for executors used to load/decode/transform data not found in\n   * cache.\n   */\n  static final String DEFAULT_SOURCE_EXECUTOR_NAME = \"source\";\n\n  /**\n   * The default thread name prefix for executors used to load/decode/transform data found in\n   * Glide's cache.\n   */\n  static final String DEFAULT_DISK_CACHE_EXECUTOR_NAME = \"disk-cache\";\n\n  /**\n   * The default thread count for executors used to load/decode/transform data found in Glide's\n   * cache.\n   */\n  static final int DEFAULT_DISK_CACHE_EXECUTOR_THREADS = 1;\n\n  private static final String TAG = \"GlideExecutor\";\n\n  /**\n   * The default thread name prefix for executors from unlimited thread pool used to\n   * load/decode/transform data not found in cache.\n   */\n  private static final String DEFAULT_SOURCE_UNLIMITED_EXECUTOR_NAME = \"source-unlimited\";\n\n  static final String DEFAULT_ANIMATION_EXECUTOR_NAME = \"animation\";\n\n  /** The default keep alive time for threads in our cached thread pools in milliseconds. */\n  private static final long KEEP_ALIVE_TIME_MS = TimeUnit.SECONDS.toMillis(10);\n\n  // Don't use more than four threads when automatically determining thread count..\n  private static final int MAXIMUM_AUTOMATIC_THREAD_COUNT = 4;\n\n  // May be accessed on other threads, but this is an optimization only so it's ok if we set its\n  // value more than once.\n  private static volatile int bestThreadCount;\n\n  private final ExecutorService delegate;\n\n  /** The default priority for threads created by Glide. */\n  public static final int DEFAULT_PRIORITY =\n      android.os.Process.THREAD_PRIORITY_BACKGROUND\n          + android.os.Process.THREAD_PRIORITY_MORE_FAVORABLE;\n\n  /**\n   * Returns a new {@link Builder} with the {@link #DEFAULT_DISK_CACHE_EXECUTOR_THREADS} threads,\n   * {@link #DEFAULT_DISK_CACHE_EXECUTOR_NAME} name and {@link UncaughtThrowableStrategy#DEFAULT}\n   * uncaught throwable strategy.\n   *\n   * <p>Disk cache executors do not allow network operations on their threads.\n   */\n  public static GlideExecutor.Builder newDiskCacheBuilder() {\n    return new GlideExecutor.Builder(/* preventNetworkOperations= */ true)\n        .setThreadCount(DEFAULT_DISK_CACHE_EXECUTOR_THREADS)\n        .setName(DEFAULT_DISK_CACHE_EXECUTOR_NAME);\n  }\n\n  /** Shortcut for calling {@link Builder#build()} on {@link #newDiskCacheBuilder()}. */\n  public static GlideExecutor newDiskCacheExecutor() {\n    return newDiskCacheBuilder().build();\n  }\n\n  /**\n   * @deprecated Use {@link #newDiskCacheBuilder()} and {@link\n   *     Builder#setUncaughtThrowableStrategy(UncaughtThrowableStrategy)} instead.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  @Deprecated\n  public static GlideExecutor newDiskCacheExecutor(\n      UncaughtThrowableStrategy uncaughtThrowableStrategy) {\n    return newDiskCacheBuilder().setUncaughtThrowableStrategy(uncaughtThrowableStrategy).build();\n  }\n\n  /**\n   * @deprecated Use {@link #newDiskCacheBuilder()} instead.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @Deprecated\n  public static GlideExecutor newDiskCacheExecutor(\n      int threadCount, String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {\n    return newDiskCacheBuilder()\n        .setThreadCount(threadCount)\n        .setName(name)\n        .setUncaughtThrowableStrategy(uncaughtThrowableStrategy)\n        .build();\n  }\n\n  /**\n   * Returns a new {@link Builder} with the default thread count returned from {@link\n   * #calculateBestThreadCount()}, the {@link #DEFAULT_SOURCE_EXECUTOR_NAME} thread name prefix, and\n   * the {@link\n   * com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy#DEFAULT}\n   * uncaught throwable strategy.\n   *\n   * <p>Source executors allow network operations on their threads.\n   */\n  public static GlideExecutor.Builder newSourceBuilder() {\n    return new GlideExecutor.Builder(/* preventNetworkOperations= */ false)\n        .setThreadCount(calculateBestThreadCount())\n        .setName(DEFAULT_SOURCE_EXECUTOR_NAME);\n  }\n\n  /** Shortcut for calling {@link Builder#build()} on {@link #newSourceBuilder()}. */\n  public static GlideExecutor newSourceExecutor() {\n    return newSourceBuilder().build();\n  }\n\n  /**\n   * @deprecated Use {@link #newSourceBuilder()} instead.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  @Deprecated\n  public static GlideExecutor newSourceExecutor(\n      UncaughtThrowableStrategy uncaughtThrowableStrategy) {\n    return newSourceBuilder().setUncaughtThrowableStrategy(uncaughtThrowableStrategy).build();\n  }\n\n  /**\n   * @deprecated Use {@link #newSourceBuilder()} instead.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @Deprecated\n  public static GlideExecutor newSourceExecutor(\n      int threadCount, String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {\n    return newSourceBuilder()\n        .setThreadCount(threadCount)\n        .setName(name)\n        .setUncaughtThrowableStrategy(uncaughtThrowableStrategy)\n        .build();\n  }\n\n  /**\n   * Returns a new unlimited thread pool with zero core thread count to make sure no threads are\n   * created by default, {@link #KEEP_ALIVE_TIME_MS} keep alive time, the {@link\n   * #DEFAULT_SOURCE_UNLIMITED_EXECUTOR_NAME} thread name prefix, the {@link\n   * com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy#DEFAULT}\n   * uncaught throwable strategy, and the {@link SynchronousQueue} since using default unbounded\n   * blocking queue, for example, {@link PriorityBlockingQueue} effectively won't create more than\n   * {@code corePoolSize} threads. See <a href=\n   * \"http://developer.android.com/reference/java/util/concurrent/ThreadPoolExecutor.html\">\n   * ThreadPoolExecutor documentation</a>.\n   *\n   * <p>Source executors allow network operations on their threads.\n   */\n  public static GlideExecutor newUnlimitedSourceExecutor() {\n    return new GlideExecutor(\n        new ThreadPoolExecutor(\n            0,\n            Integer.MAX_VALUE,\n            KEEP_ALIVE_TIME_MS,\n            TimeUnit.MILLISECONDS,\n            new SynchronousQueue<Runnable>(),\n            new DefaultThreadFactory(\n                new DefaultPriorityThreadFactory(),\n                DEFAULT_SOURCE_UNLIMITED_EXECUTOR_NAME,\n                UncaughtThrowableStrategy.DEFAULT,\n                false)));\n  }\n\n  /**\n   * Returns a new fixed thread pool that defaults to either one or two threads depending on the\n   * number of available cores to use when loading frames of animations.\n   *\n   * <p>Animation executors do not allow network operations on their threads.\n   */\n  public static GlideExecutor.Builder newAnimationBuilder() {\n    int maximumPoolSize = calculateAnimationExecutorThreadCount();\n    return new GlideExecutor.Builder(/* preventNetworkOperations= */ true)\n        .setThreadCount(maximumPoolSize)\n        .setName(DEFAULT_ANIMATION_EXECUTOR_NAME);\n  }\n\n  static int calculateAnimationExecutorThreadCount() {\n    int bestThreadCount = calculateBestThreadCount();\n    // We don't want to add a ton of threads running animations in parallel with our source and\n    // disk cache executors. Doing so adds unnecessary CPU load and can also dramatically increase\n    // our maximum memory usage. Typically one thread is sufficient here, but for higher end devices\n    // with more cores, two threads can provide better performance if lots of GIFs are showing at\n    // once.\n    return bestThreadCount >= 4 ? 2 : 1;\n  }\n\n  /** Shortcut for calling {@link Builder#build()} on {@link #newAnimationBuilder()}. */\n  public static GlideExecutor newAnimationExecutor() {\n    return newAnimationBuilder().build();\n  }\n\n  /**\n   * @deprecated Use {@link #newAnimationBuilder()} instead.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @Deprecated\n  public static GlideExecutor newAnimationExecutor(\n      int threadCount, UncaughtThrowableStrategy uncaughtThrowableStrategy) {\n    return newAnimationBuilder()\n        .setThreadCount(threadCount)\n        .setUncaughtThrowableStrategy(uncaughtThrowableStrategy)\n        .build();\n  }\n\n  @VisibleForTesting\n  GlideExecutor(ExecutorService delegate) {\n    this.delegate = delegate;\n  }\n\n  @Override\n  public void execute(@NonNull Runnable command) {\n    delegate.execute(command);\n  }\n\n  @NonNull\n  @Override\n  public Future<?> submit(@NonNull Runnable task) {\n    return delegate.submit(task);\n  }\n\n  @NonNull\n  @Override\n  public <T> List<Future<T>> invokeAll(@NonNull Collection<? extends Callable<T>> tasks)\n      throws InterruptedException {\n    return delegate.invokeAll(tasks);\n  }\n\n  @NonNull\n  @Override\n  public <T> List<Future<T>> invokeAll(\n      @NonNull Collection<? extends Callable<T>> tasks, long timeout, @NonNull TimeUnit unit)\n      throws InterruptedException {\n    return delegate.invokeAll(tasks, timeout, unit);\n  }\n\n  @NonNull\n  @Override\n  public <T> T invokeAny(@NonNull Collection<? extends Callable<T>> tasks)\n      throws InterruptedException, ExecutionException {\n    return delegate.invokeAny(tasks);\n  }\n\n  @Override\n  public <T> T invokeAny(\n      @NonNull Collection<? extends Callable<T>> tasks, long timeout, @NonNull TimeUnit unit)\n      throws InterruptedException, ExecutionException, TimeoutException {\n    return delegate.invokeAny(tasks, timeout, unit);\n  }\n\n  @NonNull\n  @Override\n  public <T> Future<T> submit(@NonNull Runnable task, T result) {\n    return delegate.submit(task, result);\n  }\n\n  @Override\n  public <T> Future<T> submit(@NonNull Callable<T> task) {\n    return delegate.submit(task);\n  }\n\n  @Override\n  public void shutdown() {\n    delegate.shutdown();\n  }\n\n  @NonNull\n  @Override\n  public List<Runnable> shutdownNow() {\n    return delegate.shutdownNow();\n  }\n\n  @Override\n  public boolean isShutdown() {\n    return delegate.isShutdown();\n  }\n\n  @Override\n  public boolean isTerminated() {\n    return delegate.isTerminated();\n  }\n\n  @Override\n  public boolean awaitTermination(long timeout, @NonNull TimeUnit unit)\n      throws InterruptedException {\n    return delegate.awaitTermination(timeout, unit);\n  }\n\n  @Override\n  public String toString() {\n    return delegate.toString();\n  }\n\n  /** Determines the number of cores available on the device. */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static int calculateBestThreadCount() {\n    if (bestThreadCount == 0) {\n      bestThreadCount =\n          Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT, RuntimeCompat.availableProcessors());\n    }\n    return bestThreadCount;\n  }\n\n  /**\n   * A strategy for handling unexpected and uncaught {@link Throwable}s thrown by futures run on the\n   * pool.\n   */\n  public interface UncaughtThrowableStrategy {\n    /** Silently catches and ignores the uncaught {@link Throwable}s. */\n    // Public API.\n    @SuppressWarnings(\"unused\")\n    UncaughtThrowableStrategy IGNORE =\n        new UncaughtThrowableStrategy() {\n          @Override\n          public void handle(Throwable t) {\n            // ignore\n          }\n        };\n\n    /** Logs the uncaught {@link Throwable}s using {@link #TAG} and {@link Log}. */\n    UncaughtThrowableStrategy LOG =\n        new UncaughtThrowableStrategy() {\n          @Override\n          public void handle(Throwable t) {\n            if (t != null && Log.isLoggable(TAG, Log.ERROR)) {\n              Log.e(TAG, \"Request threw uncaught throwable\", t);\n            }\n          }\n        };\n\n    /** Rethrows the uncaught {@link Throwable}s to crash the app. */\n    // Public API.\n    @SuppressWarnings(\"unused\")\n    UncaughtThrowableStrategy THROW =\n        new UncaughtThrowableStrategy() {\n          @Override\n          public void handle(Throwable t) {\n            if (t != null) {\n              throw new RuntimeException(\"Request threw uncaught throwable\", t);\n            }\n          }\n        };\n\n    /** The default strategy, currently {@link #LOG}. */\n    UncaughtThrowableStrategy DEFAULT = LOG;\n\n    void handle(Throwable t);\n  }\n\n  private static final class DefaultPriorityThreadFactory implements ThreadFactory {\n\n    @Override\n    public Thread newThread(@NonNull Runnable runnable) {\n      return new Thread(runnable) {\n        @Override\n        public void run() {\n          // why PMD suppression is needed: https://github.com/pmd/pmd/issues/808\n          android.os.Process.setThreadPriority(DEFAULT_PRIORITY); // NOPMD AccessorMethodGeneration\n          super.run();\n        }\n      };\n    }\n  }\n\n  /**\n   * A {@link java.util.concurrent.ThreadFactory} that builds threads slightly above priority {@link\n   * android.os.Process#THREAD_PRIORITY_BACKGROUND}.\n   */\n  private static final class DefaultThreadFactory implements ThreadFactory {\n\n    private final ThreadFactory delegate;\n    private final String name;\n    @Synthetic final UncaughtThrowableStrategy uncaughtThrowableStrategy;\n    @Synthetic final boolean preventNetworkOperations;\n    private final AtomicInteger threadNum = new AtomicInteger();\n\n    DefaultThreadFactory(\n        ThreadFactory delegate,\n        String name,\n        UncaughtThrowableStrategy uncaughtThrowableStrategy,\n        boolean preventNetworkOperations) {\n      this.delegate = delegate;\n      this.name = name;\n      this.uncaughtThrowableStrategy = uncaughtThrowableStrategy;\n      this.preventNetworkOperations = preventNetworkOperations;\n    }\n\n    @Override\n    public Thread newThread(@NonNull final Runnable runnable) {\n      Thread newThread =\n          delegate.newThread(\n              new Runnable() {\n                @Override\n                public void run() {\n                  if (preventNetworkOperations) {\n                    StrictMode.setThreadPolicy(\n                        new ThreadPolicy.Builder().detectNetwork().penaltyDeath().build());\n                  }\n                  try {\n                    runnable.run();\n                  } catch (Throwable t) {\n                    uncaughtThrowableStrategy.handle(t);\n                  }\n                }\n              });\n      newThread.setName(\"glide-\" + name + \"-thread-\" + threadNum.getAndIncrement());\n      return newThread;\n    }\n  }\n\n  /** A builder for {@link GlideExecutor}s. */\n  public static final class Builder {\n    /**\n     * Prevents core and non-core threads from timing out ever if provided to {@link\n     * #setThreadTimeoutMillis(long)}.\n     */\n    public static final long NO_THREAD_TIMEOUT = 0L;\n\n    private final boolean preventNetworkOperations;\n\n    private int corePoolSize;\n    private int maximumPoolSize;\n\n    @NonNull private ThreadFactory threadFactory = new DefaultPriorityThreadFactory();\n\n    @NonNull\n    private UncaughtThrowableStrategy uncaughtThrowableStrategy = UncaughtThrowableStrategy.DEFAULT;\n\n    private String name;\n    private long threadTimeoutMillis;\n    private Function<? super Runnable, ? extends Runnable> onExecuteDecorator;\n\n    @Synthetic\n    Builder(boolean preventNetworkOperations) {\n      this.preventNetworkOperations = preventNetworkOperations;\n    }\n\n    /**\n     * Allows both core and non-core threads in the executor to be terminated if no tasks arrive for\n     * at least the given timeout milliseconds.\n     *\n     * <p>Use {@link #NO_THREAD_TIMEOUT} to remove a previously set timeout.\n     */\n    public Builder setThreadTimeoutMillis(long threadTimeoutMillis) {\n      this.threadTimeoutMillis = threadTimeoutMillis;\n      return this;\n    }\n\n    /** Sets the maximum number of threads to use. */\n    public Builder setThreadCount(@IntRange(from = 1) int threadCount) {\n      corePoolSize = threadCount;\n      maximumPoolSize = threadCount;\n      return this;\n    }\n\n    /**\n     * Sets the {@link ThreadFactory} responsible for creating threads and setting their priority.\n     *\n     * <p>Usage of this method may override other options on this builder. No guarantees are\n     * provided with regards to the behavior of this method or how it interacts with other methods\n     * on the builder. Use at your own risk.\n     *\n     * @deprecated This is an experimental method that may be removed without warning in a future\n     *     version.\n     */\n    @Deprecated\n    public Builder setThreadFactory(@NonNull ThreadFactory threadFactory) {\n      this.threadFactory = threadFactory;\n      return this;\n    }\n\n    /**\n     * Sets the {@link UncaughtThrowableStrategy} to use for unexpected exceptions thrown by tasks\n     * on {@link GlideExecutor}s built by this {@code Builder}.\n     */\n    public Builder setUncaughtThrowableStrategy(@NonNull UncaughtThrowableStrategy strategy) {\n      this.uncaughtThrowableStrategy = strategy;\n      return this;\n    }\n\n    /**\n     * Sets the prefix to use for each thread name created by any {@link GlideExecutor}s built by\n     * this {@code Builder}.\n     */\n    public Builder setName(String name) {\n      this.name = name;\n      return this;\n    }\n\n    /**\n     * Sets the decorator to be applied to each runnable executed by the executor.\n     *\n     * <p>This is an experimental method that may be removed without warning in a future version.\n     */\n    public Builder experimentalSetOnExecuteDecorator(\n        Function<? super Runnable, ? extends Runnable> onExecuteDecorator) {\n      this.onExecuteDecorator = onExecuteDecorator;\n      return this;\n    }\n\n    /** Builds a new {@link GlideExecutor} with any previously specified options. */\n    public GlideExecutor build() {\n      if (TextUtils.isEmpty(name)) {\n        throw new IllegalArgumentException(\n            \"Name must be non-null and non-empty, but given: \" + name);\n      }\n      ThreadFactory factory =\n          new DefaultThreadFactory(\n              threadFactory, name, uncaughtThrowableStrategy, preventNetworkOperations);\n      ThreadPoolExecutor executor;\n      if (onExecuteDecorator != null) {\n        executor =\n            new ThreadPoolExecutor(\n                corePoolSize,\n                maximumPoolSize,\n                /* keepAliveTime= */ threadTimeoutMillis,\n                TimeUnit.MILLISECONDS,\n                new PriorityBlockingQueue<>(),\n                factory) {\n              @Override\n              public void execute(@NonNull Runnable command) {\n                super.execute(onExecuteDecorator.apply(command));\n              }\n            };\n      } else {\n        executor =\n            new ThreadPoolExecutor(\n                corePoolSize,\n                maximumPoolSize,\n                /* keepAliveTime= */ threadTimeoutMillis,\n                TimeUnit.MILLISECONDS,\n                new PriorityBlockingQueue<>(),\n                factory);\n      }\n\n      if (threadTimeoutMillis != NO_THREAD_TIMEOUT) {\n        executor.allowCoreThreadTimeOut(true);\n      }\n\n      return new GlideExecutor(executor);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/executor/RuntimeCompat.java",
    "content": "package com.bumptech.glide.load.engine.executor;\n\nimport android.os.Build;\nimport android.os.StrictMode;\nimport android.os.StrictMode.ThreadPolicy;\nimport android.util.Log;\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.util.regex.Pattern;\n\n/** Compatibility methods for {@link java.lang.Runtime}. */\nfinal class RuntimeCompat {\n  private static final String TAG = \"GlideRuntimeCompat\";\n  private static final String CPU_NAME_REGEX = \"cpu[0-9]+\";\n  private static final String CPU_LOCATION = \"/sys/devices/system/cpu/\";\n\n  private RuntimeCompat() {\n    // Utility class.\n  }\n\n  /** Determines the number of cores available on the device. */\n  static int availableProcessors() {\n    int cpus = Runtime.getRuntime().availableProcessors();\n    if (Build.VERSION.SDK_INT < 17) {\n      cpus = Math.max(getCoreCountPre17(), cpus);\n    }\n    return cpus;\n  }\n\n  /**\n   * Determines the number of cores available on the device (pre-v17).\n   *\n   * <p>Before Jellybean, {@link Runtime#availableProcessors()} returned the number of awake cores,\n   * which may not be the number of available cores depending on the device's current state. See\n   * https://stackoverflow.com/a/30150409.\n   *\n   * @return the maximum number of processors available to the VM; never smaller than one\n   */\n  @SuppressWarnings(\"PMD\")\n  private static int getCoreCountPre17() {\n    // We override the current ThreadPolicy to allow disk reads.\n    // This shouldn't actually do disk-IO and accesses a device file.\n    // See: https://github.com/bumptech/glide/issues/1170\n    File[] cpus = null;\n    ThreadPolicy originalPolicy = StrictMode.allowThreadDiskReads();\n    try {\n      File cpuInfo = new File(CPU_LOCATION);\n      final Pattern cpuNamePattern = Pattern.compile(CPU_NAME_REGEX);\n      cpus =\n          cpuInfo.listFiles(\n              new FilenameFilter() {\n                @Override\n                public boolean accept(File file, String s) {\n                  return cpuNamePattern.matcher(s).matches();\n                }\n              });\n    } catch (Throwable t) {\n      if (Log.isLoggable(TAG, Log.ERROR)) {\n        Log.e(TAG, \"Failed to calculate accurate cpu count\", t);\n      }\n    } finally {\n      StrictMode.setThreadPolicy(originalPolicy);\n    }\n    return Math.max(1, cpus != null ? cpus.length : 0);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunner.java",
    "content": "package com.bumptech.glide.load.engine.prefill;\n\nimport android.graphics.Bitmap;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.SystemClock;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.load.resource.bitmap.BitmapResource;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.security.MessageDigest;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A class that allocates {@link android.graphics.Bitmap Bitmaps} to make sure that the {@link\n * com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} is pre-populated.\n *\n * <p>By posting to the main thread with backoffs, we try to avoid ANRs when the garbage collector\n * gets into a state where a high percentage of {@link Bitmap} allocations trigger a stop the world\n * GC. We try to detect whether or not a GC has occurred by only allowing our allocator to run for a\n * limited number of milliseconds. Since the allocations themselves very fast, a GC is the most\n * likely reason for a substantial delay. If we detect our allocator has run for more than our\n * limit, we assume a GC has occurred, stop the current allocations, and try again after a delay.\n */\nfinal class BitmapPreFillRunner implements Runnable {\n  @VisibleForTesting static final String TAG = \"PreFillRunner\";\n  private static final Clock DEFAULT_CLOCK = new Clock();\n\n  /**\n   * The maximum number of millis we can run before posting. Set to match and detect the duration of\n   * non concurrent GCs.\n   */\n  static final long MAX_DURATION_MS = 32;\n\n  /**\n   * The amount of time in ms we wait before continuing to allocate after the first GC is detected.\n   */\n  static final long INITIAL_BACKOFF_MS = 40;\n\n  /** The amount by which the current backoff time is multiplied each time we detect a GC. */\n  static final int BACKOFF_RATIO = 4;\n\n  /** The maximum amount of time in ms we wait before continuing to allocate. */\n  static final long MAX_BACKOFF_MS = TimeUnit.SECONDS.toMillis(1);\n\n  private final BitmapPool bitmapPool;\n  private final MemoryCache memoryCache;\n  private final PreFillQueue toPrefill;\n  private final Clock clock;\n  private final Set<PreFillType> seenTypes = new HashSet<>();\n  private final Handler handler;\n\n  private long currentDelay = INITIAL_BACKOFF_MS;\n  private boolean isCancelled;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public BitmapPreFillRunner(\n      BitmapPool bitmapPool, MemoryCache memoryCache, PreFillQueue allocationOrder) {\n    this(\n        bitmapPool,\n        memoryCache,\n        allocationOrder,\n        DEFAULT_CLOCK,\n        new Handler(Looper.getMainLooper()));\n  }\n\n  @VisibleForTesting\n  BitmapPreFillRunner(\n      BitmapPool bitmapPool,\n      MemoryCache memoryCache,\n      PreFillQueue allocationOrder,\n      Clock clock,\n      Handler handler) {\n    this.bitmapPool = bitmapPool;\n    this.memoryCache = memoryCache;\n    this.toPrefill = allocationOrder;\n    this.clock = clock;\n    this.handler = handler;\n  }\n\n  public void cancel() {\n    isCancelled = true;\n  }\n\n  /**\n   * Attempts to allocate {@link android.graphics.Bitmap}s and returns {@code true} if there are\n   * more {@link android.graphics.Bitmap}s to allocate and {@code false} otherwise.\n   */\n  @VisibleForTesting\n  boolean allocate() {\n    long start = clock.now();\n    while (!toPrefill.isEmpty() && !isGcDetected(start)) {\n      PreFillType toAllocate = toPrefill.remove();\n      final Bitmap bitmap;\n      if (!seenTypes.contains(toAllocate)) {\n        seenTypes.add(toAllocate);\n        bitmap =\n            bitmapPool.getDirty(\n                toAllocate.getWidth(), toAllocate.getHeight(), toAllocate.getConfig());\n      } else {\n        bitmap =\n            Bitmap.createBitmap(\n                toAllocate.getWidth(), toAllocate.getHeight(), toAllocate.getConfig());\n      }\n\n      // Order matters here! If the Bitmap is too large or the BitmapPool is too full, it may be\n      // recycled after the call to bitmapPool#put below.\n      int bitmapSize = Util.getBitmapByteSize(bitmap);\n\n      // Don't over fill the memory cache to avoid evicting useful resources, but make sure it's\n      // not empty so that we use all available space.\n      if (getFreeMemoryCacheBytes() >= bitmapSize) {\n        // We could probably make UniqueKey just always return false from equals,\n        // but the allocation of the Key is not nearly as expensive as the allocation of the Bitmap,\n        // so it's probably not worth it.\n        @SuppressWarnings(\"PMD.AvoidInstantiatingObjectsInLoops\")\n        Key uniqueKey = new UniqueKey();\n        memoryCache.put(uniqueKey, BitmapResource.obtain(bitmap, bitmapPool));\n      } else {\n        bitmapPool.put(bitmap);\n      }\n\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"allocated [\"\n                + toAllocate.getWidth()\n                + \"x\"\n                + toAllocate.getHeight()\n                + \"] \"\n                + toAllocate.getConfig()\n                + \" size: \"\n                + bitmapSize);\n      }\n    }\n\n    return !isCancelled && !toPrefill.isEmpty();\n  }\n\n  private boolean isGcDetected(long startTimeMs) {\n    return clock.now() - startTimeMs >= MAX_DURATION_MS;\n  }\n\n  private long getFreeMemoryCacheBytes() {\n    return memoryCache.getMaxSize() - memoryCache.getCurrentSize();\n  }\n\n  @Override\n  public void run() {\n    if (allocate()) {\n      handler.postDelayed(this, getNextDelay());\n    }\n  }\n\n  private long getNextDelay() {\n    long result = currentDelay;\n    currentDelay = Math.min(currentDelay * BACKOFF_RATIO, MAX_BACKOFF_MS);\n    return result;\n  }\n\n  private static final class UniqueKey implements Key {\n\n    @Synthetic\n    @SuppressWarnings(\"WeakerAccess\")\n    UniqueKey() {}\n\n    @Override\n    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n      throw new UnsupportedOperationException();\n    }\n  }\n\n  @VisibleForTesting\n  static class Clock {\n    long now() {\n      return SystemClock.currentThreadTimeMillis();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java",
    "content": "package com.bumptech.glide.load.engine.prefill;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.util.Util;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * A class for pre-filling {@link android.graphics.Bitmap Bitmaps} in a {@link\n * com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}.\n */\npublic final class BitmapPreFiller {\n\n  private final MemoryCache memoryCache;\n  private final BitmapPool bitmapPool;\n  private final DecodeFormat defaultFormat;\n\n  private BitmapPreFillRunner current;\n\n  public BitmapPreFiller(\n      MemoryCache memoryCache, BitmapPool bitmapPool, DecodeFormat defaultFormat) {\n    this.memoryCache = memoryCache;\n    this.bitmapPool = bitmapPool;\n    this.defaultFormat = defaultFormat;\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  public void preFill(PreFillType.Builder... bitmapAttributeBuilders) {\n    if (current != null) {\n      current.cancel();\n    }\n\n    PreFillType[] bitmapAttributes = new PreFillType[bitmapAttributeBuilders.length];\n    for (int i = 0; i < bitmapAttributeBuilders.length; i++) {\n      PreFillType.Builder builder = bitmapAttributeBuilders[i];\n      if (builder.getConfig() == null) {\n        builder.setConfig(\n            defaultFormat == DecodeFormat.PREFER_ARGB_8888\n                ? Bitmap.Config.ARGB_8888\n                : Bitmap.Config.RGB_565);\n      }\n      bitmapAttributes[i] = builder.build();\n    }\n\n    PreFillQueue allocationOrder = generateAllocationOrder(bitmapAttributes);\n    current = new BitmapPreFillRunner(bitmapPool, memoryCache, allocationOrder);\n    Util.postOnUiThread(current);\n  }\n\n  @VisibleForTesting\n  PreFillQueue generateAllocationOrder(PreFillType... preFillSizes) {\n    final long maxSize =\n        memoryCache.getMaxSize() - memoryCache.getCurrentSize() + bitmapPool.getMaxSize();\n\n    int totalWeight = 0;\n    for (PreFillType size : preFillSizes) {\n      totalWeight += size.getWeight();\n    }\n\n    final float bytesPerWeight = maxSize / (float) totalWeight;\n\n    Map<PreFillType, Integer> attributeToCount = new HashMap<>();\n    for (PreFillType size : preFillSizes) {\n      int bytesForSize = Math.round(bytesPerWeight * size.getWeight());\n      int bytesPerBitmap = getSizeInBytes(size);\n      int bitmapsForSize = bytesForSize / bytesPerBitmap;\n      attributeToCount.put(size, bitmapsForSize);\n    }\n\n    return new PreFillQueue(attributeToCount);\n  }\n\n  private static int getSizeInBytes(PreFillType size) {\n    return Util.getBitmapByteSize(size.getWidth(), size.getHeight(), size.getConfig());\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/prefill/PreFillQueue.java",
    "content": "package com.bumptech.glide.load.engine.prefill;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nfinal class PreFillQueue {\n\n  private final Map<PreFillType, Integer> bitmapsPerType;\n  private final List<PreFillType> keyList;\n  private int bitmapsRemaining;\n  private int keyIndex;\n\n  public PreFillQueue(Map<PreFillType, Integer> bitmapsPerType) {\n    this.bitmapsPerType = bitmapsPerType;\n    // We don't particularly care about the initial order.\n    keyList = new ArrayList<>(bitmapsPerType.keySet());\n\n    for (Integer count : bitmapsPerType.values()) {\n      bitmapsRemaining += count;\n    }\n  }\n\n  public PreFillType remove() {\n    PreFillType result = keyList.get(keyIndex);\n\n    Integer countForResult = bitmapsPerType.get(result);\n    if (countForResult == 1) {\n      bitmapsPerType.remove(result);\n      keyList.remove(keyIndex);\n    } else {\n      bitmapsPerType.put(result, countForResult - 1);\n    }\n    bitmapsRemaining--;\n\n    // Avoid divide by 0.\n    keyIndex = keyList.isEmpty() ? 0 : (keyIndex + 1) % keyList.size();\n\n    return result;\n  }\n\n  public int getSize() {\n    return bitmapsRemaining;\n  }\n\n  public boolean isEmpty() {\n    return bitmapsRemaining == 0;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/engine/prefill/PreFillType.java",
    "content": "package com.bumptech.glide.load.engine.prefill;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * A container for a put of options used to pre-fill a {@link\n * com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} with {@link Bitmap Bitmaps} of a single\n * size and configuration.\n */\npublic final class PreFillType {\n  @VisibleForTesting static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.RGB_565;\n  private final int width;\n  private final int height;\n  private final Bitmap.Config config;\n  private final int weight;\n\n  /**\n   * Constructor for a single type of {@link android.graphics.Bitmap}.\n   *\n   * @param width The width in pixels of the {@link android.graphics.Bitmap Bitmaps} to pre-fill.\n   * @param height The height in pixels of the {@link android.graphics.Bitmap Bitmaps} to pre-fill.\n   * @param config The {@link android.graphics.Bitmap.Config} of the {@link android.graphics.Bitmap\n   *     Bitmaps} to pre-fill.\n   * @param weight An integer indicating how to balance pre-filling this size and configuration of\n   *     {@link android.graphics.Bitmap} against any other sizes/configurations that may be being\n   *     pre-filled.\n   */\n  PreFillType(int width, int height, Bitmap.Config config, int weight) {\n    this.config = Preconditions.checkNotNull(config, \"Config must not be null\");\n    this.width = width;\n    this.height = height;\n    this.weight = weight;\n  }\n\n  /** Returns the width in pixels of the {@link android.graphics.Bitmap Bitmaps}. */\n  int getWidth() {\n    return width;\n  }\n\n  /** Returns the height in pixels of the {@link android.graphics.Bitmap Bitmaps}. */\n  int getHeight() {\n    return height;\n  }\n\n  /**\n   * Returns the {@link android.graphics.Bitmap.Config} of the {@link android.graphics.Bitmap\n   * Bitmaps}.\n   */\n  Bitmap.Config getConfig() {\n    return config;\n  }\n\n  /** Returns the weight of the {@link android.graphics.Bitmap Bitmaps} of this type. */\n  int getWeight() {\n    return weight;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof PreFillType) {\n      PreFillType other = (PreFillType) o;\n      return height == other.height\n          && width == other.width\n          && weight == other.weight\n          && config == other.config;\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    int result = width;\n    result = 31 * result + height;\n    result = 31 * result + config.hashCode();\n    result = 31 * result + weight;\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return \"PreFillSize{\"\n        + \"width=\"\n        + width\n        + \", height=\"\n        + height\n        + \", config=\"\n        + config\n        + \", weight=\"\n        + weight\n        + '}';\n  }\n\n  /** Builder for {@link PreFillType}. */\n  public static class Builder {\n    private final int width;\n    private final int height;\n\n    private Bitmap.Config config;\n    private int weight = 1;\n\n    /**\n     * Constructor for a builder that uses the given size as the width and height of the Bitmaps to\n     * prefill.\n     *\n     * @param size The width and height in pixels of the Bitmaps to prefill.\n     */\n    public Builder(int size) {\n      this(size, size);\n    }\n\n    /**\n     * Constructor for a builder that uses the given dimensions as the dimensions of the Bitmaps to\n     * prefill.\n     *\n     * @param width The width in pixels of the Bitmaps to prefill.\n     * @param height The height in pixels of the Bitmaps to prefill.\n     */\n    public Builder(int width, int height) {\n      if (width <= 0) {\n        throw new IllegalArgumentException(\"Width must be > 0\");\n      }\n      if (height <= 0) {\n        throw new IllegalArgumentException(\"Height must be > 0\");\n      }\n      this.width = width;\n      this.height = height;\n    }\n\n    /**\n     * Sets the {@link android.graphics.Bitmap.Config} for the Bitmaps to pre-fill.\n     *\n     * @param config The config to use, or null to use Glide's default.\n     * @return This builder.\n     */\n    public Builder setConfig(@Nullable Bitmap.Config config) {\n      this.config = config;\n      return this;\n    }\n\n    /** Returns the current {@link android.graphics.Bitmap.Config}. */\n    Bitmap.Config getConfig() {\n      return config;\n    }\n\n    /**\n     * Sets the weight to use to balance how many Bitmaps of this type are prefilled relative to the\n     * other requested types.\n     *\n     * @param weight An integer indicating how to balance pre-filling this size and configuration of\n     *     {@link android.graphics.Bitmap} against any other sizes/configurations that may be being\n     *     pre-filled.\n     * @return This builder.\n     */\n    public Builder setWeight(int weight) {\n      if (weight <= 0) {\n        throw new IllegalArgumentException(\"Weight must be > 0\");\n      }\n      this.weight = weight;\n      return this;\n    }\n\n    /** Returns a new {@link PreFillType}. */\n    PreFillType build() {\n      return new PreFillType(width, height, config, weight);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/AssetUriLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.content.ContentResolver;\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.AssetManager;\nimport android.net.Uri;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.FileDescriptorAssetPathFetcher;\nimport com.bumptech.glide.load.data.StreamAssetPathFetcher;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.InputStream;\n\n/**\n * Loads a specific data type from an Asset Manager Uri.\n *\n * @param <Data> The type of data this loader will obtain.\n */\npublic class AssetUriLoader<Data> implements ModelLoader<Uri, Data> {\n  private static final String ASSET_PATH_SEGMENT = \"android_asset\";\n  private static final String ASSET_PREFIX =\n      ContentResolver.SCHEME_FILE + \":///\" + ASSET_PATH_SEGMENT + \"/\";\n  private static final int ASSET_PREFIX_LENGTH = ASSET_PREFIX.length();\n\n  private final AssetManager assetManager;\n  private final AssetFetcherFactory<Data> factory;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public AssetUriLoader(AssetManager assetManager, AssetFetcherFactory<Data> factory) {\n    this.assetManager = assetManager;\n    this.factory = factory;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull Uri model, int width, int height, @NonNull Options options) {\n    String assetPath = model.toString().substring(ASSET_PREFIX_LENGTH);\n    return new LoadData<>(new ObjectKey(model), factory.buildFetcher(assetManager, assetPath));\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri model) {\n    return ContentResolver.SCHEME_FILE.equals(model.getScheme())\n        && !model.getPathSegments().isEmpty()\n        && ASSET_PATH_SEGMENT.equals(model.getPathSegments().get(0));\n  }\n\n  /**\n   * A factory to build a {@link DataFetcher} for a specific asset path.\n   *\n   * @param <Data> The type of data that will be obtained by the fetcher.\n   */\n  public interface AssetFetcherFactory<Data> {\n    DataFetcher<Data> buildFetcher(AssetManager assetManager, String assetPath);\n  }\n\n  /** Factory for loading {@link InputStream}s from asset manager Uris. */\n  public static class StreamFactory\n      implements ModelLoaderFactory<Uri, InputStream>, AssetFetcherFactory<InputStream> {\n\n    private final AssetManager assetManager;\n\n    public StreamFactory(AssetManager assetManager) {\n      this.assetManager = assetManager;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new AssetUriLoader<>(assetManager, this);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n\n    @Override\n    public DataFetcher<InputStream> buildFetcher(AssetManager assetManager, String assetPath) {\n      return new StreamAssetPathFetcher(assetManager, assetPath);\n    }\n  }\n\n  /** Factory for loading {@link AssetFileDescriptor}s from asset manager Uris. */\n  public static class FileDescriptorFactory\n      implements ModelLoaderFactory<Uri, AssetFileDescriptor>,\n          AssetFetcherFactory<AssetFileDescriptor> {\n\n    private final AssetManager assetManager;\n\n    public FileDescriptorFactory(AssetManager assetManager) {\n      this.assetManager = assetManager;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, AssetFileDescriptor> build(MultiModelLoaderFactory multiFactory) {\n      return new AssetUriLoader<>(assetManager, this);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n\n    @Override\n    public DataFetcher<AssetFileDescriptor> buildFetcher(\n        AssetManager assetManager, String assetPath) {\n      return new FileDescriptorAssetPathFetcher(assetManager, assetPath);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ByteArrayLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\n\n/**\n * A base class to convert byte arrays to input streams so they can be decoded. This class is\n * abstract because there is no simple/quick way to generate an id from the bytes themselves, so\n * subclass must include an id.\n *\n * @param <Data> The type of data that will be loaded from a given byte array.\n */\npublic class ByteArrayLoader<Data> implements ModelLoader<byte[], Data> {\n  private final Converter<Data> converter;\n\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  public ByteArrayLoader(Converter<Data> converter) {\n    this.converter = converter;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull byte[] model, int width, int height, @NonNull Options options) {\n    return new LoadData<>(new ObjectKey(model), new Fetcher<>(model, converter));\n  }\n\n  @Override\n  public boolean handles(@NonNull byte[] model) {\n    return true;\n  }\n\n  /**\n   * Converts between a byte array a desired model class.\n   *\n   * @param <Data> The type of data to convert to.\n   */\n  public interface Converter<Data> {\n    Data convert(byte[] model);\n\n    Class<Data> getDataClass();\n  }\n\n  private static class Fetcher<Data> implements DataFetcher<Data> {\n    private final byte[] model;\n    private final Converter<Data> converter;\n\n    /**\n     * @param model We really ought to copy the model, but doing so can be hugely expensive and/or\n     *     lead to OOMs. In practice it's unlikely that users would pass an array into Glide and\n     *     then mutate it.\n     */\n    @SuppressWarnings(\"PMD.ArrayIsStoredDirectly\")\n    Fetcher(byte[] model, Converter<Data> converter) {\n      this.model = model;\n      this.converter = converter;\n    }\n\n    @Override\n    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {\n      Data result = converter.convert(model);\n      callback.onDataReady(result);\n    }\n\n    @Override\n    public void cleanup() {\n      // Do nothing.\n    }\n\n    @Override\n    public void cancel() {\n      // Do nothing.\n    }\n\n    @NonNull\n    @Override\n    public Class<Data> getDataClass() {\n      return converter.getDataClass();\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.LOCAL;\n    }\n  }\n\n  /**\n   * Factory for {@link com.bumptech.glide.load.model.ByteArrayLoader} and {@link\n   * java.nio.ByteBuffer}.\n   */\n  public static class ByteBufferFactory implements ModelLoaderFactory<byte[], ByteBuffer> {\n\n    @NonNull\n    @Override\n    public ModelLoader<byte[], ByteBuffer> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new ByteArrayLoader<>(\n          new Converter<ByteBuffer>() {\n            @Override\n            public ByteBuffer convert(byte[] model) {\n              return ByteBuffer.wrap(model);\n            }\n\n            @Override\n            public Class<ByteBuffer> getDataClass() {\n              return ByteBuffer.class;\n            }\n          });\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /** Factory for {@link ByteArrayLoader} and {@link java.io.InputStream}. */\n  public static class StreamFactory implements ModelLoaderFactory<byte[], InputStream> {\n\n    @NonNull\n    @Override\n    public ModelLoader<byte[], InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new ByteArrayLoader<>(\n          new Converter<InputStream>() {\n            @Override\n            public InputStream convert(byte[] model) {\n              return new ByteArrayInputStream(model);\n            }\n\n            @Override\n            public Class<InputStream> getDataClass() {\n              return InputStream.class;\n            }\n          });\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ByteBufferEncoder.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Encoder;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\n\n/** Writes {@link ByteBuffer ByteBuffers} to {@link File Files}. */\npublic class ByteBufferEncoder implements Encoder<ByteBuffer> {\n  private static final String TAG = \"ByteBufferEncoder\";\n\n  @Override\n  public boolean encode(@NonNull ByteBuffer data, @NonNull File file, @NonNull Options options) {\n    boolean success = false;\n    try {\n      ByteBufferUtil.toFile(data, file);\n      success = true;\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to write data\", e);\n      }\n    }\n    return success;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ByteBufferFileLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport com.bumptech.glide.util.Synthetic;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\n\n/** Loads {@link java.nio.ByteBuffer}s using NIO for {@link java.io.File}. */\npublic class ByteBufferFileLoader implements ModelLoader<File, ByteBuffer> {\n  private static final String TAG = \"ByteBufferFileLoader\";\n\n  @Override\n  public LoadData<ByteBuffer> buildLoadData(\n      @NonNull File file, int width, int height, @NonNull Options options) {\n    return new LoadData<>(new ObjectKey(file), new ByteBufferFetcher(file));\n  }\n\n  @Override\n  public boolean handles(@NonNull File file) {\n    return true;\n  }\n\n  /** Factory for {@link com.bumptech.glide.load.model.ByteBufferFileLoader}. */\n  public static class Factory implements ModelLoaderFactory<File, ByteBuffer> {\n\n    @NonNull\n    @Override\n    public ModelLoader<File, ByteBuffer> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new ByteBufferFileLoader();\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  private static final class ByteBufferFetcher implements DataFetcher<ByteBuffer> {\n\n    private final File file;\n\n    @Synthetic\n    @SuppressWarnings(\"WeakerAccess\")\n    ByteBufferFetcher(File file) {\n      this.file = file;\n    }\n\n    @Override\n    public void loadData(\n        @NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {\n      ByteBuffer result;\n      try {\n        result = ByteBufferUtil.fromFile(file);\n        callback.onDataReady(result);\n      } catch (IOException e) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Failed to obtain ByteBuffer for file\", e);\n        }\n        callback.onLoadFailed(e);\n      }\n    }\n\n    @Override\n    public void cleanup() {\n      // Do nothing.\n    }\n\n    @Override\n    public void cancel() {\n      // Do nothing.\n    }\n\n    @NonNull\n    @Override\n    public Class<ByteBuffer> getDataClass() {\n      return ByteBuffer.class;\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.LOCAL;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/DataUrlLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.util.Base64;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * A simple model loader for loading data from a Data URL String.\n *\n * <p>Data URIs use the \"data\" scheme.\n *\n * <p>See http://www.ietf.org/rfc/rfc2397.txt for a complete description of the 'data' URL scheme.\n *\n * <p>Briefly, a 'data' URL has the form:\n *\n * <pre>data:[mediatype][;base64],some_data</pre>\n *\n * @param <Model> The type of Model that we can retrieve data for, e.g. {@link String}.\n * @param <Data> The type of data that can be opened, e.g. {@link InputStream}.\n */\npublic final class DataUrlLoader<Model, Data> implements ModelLoader<Model, Data> {\n\n  private static final String DATA_SCHEME_IMAGE = \"data:image\";\n  private static final String BASE64_TAG = \";base64\";\n  private final DataDecoder<Data> dataDecoder;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public DataUrlLoader(DataDecoder<Data> dataDecoder) {\n    this.dataDecoder = dataDecoder;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull Model model, int width, int height, @NonNull Options options) {\n    return new LoadData<>(\n        new ObjectKey(model), new DataUriFetcher<>(model.toString(), dataDecoder));\n  }\n\n  @Override\n  public boolean handles(@NonNull Model model) {\n    // We expect Model to be a Uri or a String, both of which implement toString() efficiently. We\n    // should reconsider this implementation before adding any new Model types.\n    return model.toString().startsWith(DATA_SCHEME_IMAGE);\n  }\n\n  /**\n   * Allows decoding a specific type of data from a Data URL String.\n   *\n   * @param <Data> The type of data that can be opened.\n   */\n  public interface DataDecoder<Data> {\n\n    Data decode(String uri) throws IllegalArgumentException;\n\n    void close(Data data) throws IOException;\n\n    Class<Data> getDataClass();\n  }\n\n  private static final class DataUriFetcher<Data> implements DataFetcher<Data> {\n\n    private final String dataUri;\n    private final DataDecoder<Data> reader;\n    private Data data;\n\n    DataUriFetcher(String dataUri, DataDecoder<Data> reader) {\n      this.dataUri = dataUri;\n      this.reader = reader;\n    }\n\n    @Override\n    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {\n      try {\n        data = reader.decode(dataUri);\n        callback.onDataReady(data);\n      } catch (IllegalArgumentException e) {\n        callback.onLoadFailed(e);\n      }\n    }\n\n    @Override\n    public void cleanup() {\n      try {\n        reader.close(data);\n      } catch (IOException e) {\n        // Ignored.\n      }\n    }\n\n    @Override\n    public void cancel() {\n      // Do nothing.\n    }\n\n    @NonNull\n    @Override\n    public Class<Data> getDataClass() {\n      return reader.getDataClass();\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.LOCAL;\n    }\n  }\n\n  /**\n   * Factory for loading {@link InputStream}s from data uris.\n   *\n   * @param <Model> The type of Model we can obtain data for, e.g. String.\n   */\n  public static final class StreamFactory<Model> implements ModelLoaderFactory<Model, InputStream> {\n\n    private final DataDecoder<InputStream> opener;\n\n    public StreamFactory() {\n      opener =\n          new DataDecoder<InputStream>() {\n            @Override\n            public InputStream decode(String url) {\n              if (!url.startsWith(DATA_SCHEME_IMAGE)) {\n                throw new IllegalArgumentException(\"Not a valid image data URL.\");\n              }\n\n              int commaIndex = url.indexOf(',');\n              if (commaIndex == -1) {\n                throw new IllegalArgumentException(\"Missing comma in data URL.\");\n              }\n\n              String beforeComma = url.substring(0, commaIndex);\n              if (!beforeComma.endsWith(BASE64_TAG)) {\n                throw new IllegalArgumentException(\"Not a base64 image data URL.\");\n              }\n\n              String afterComma = url.substring(commaIndex + 1);\n              byte[] bytes = Base64.decode(afterComma, Base64.DEFAULT);\n\n              return new ByteArrayInputStream(bytes);\n            }\n\n            @Override\n            public void close(InputStream inputStream) throws IOException {\n              inputStream.close();\n            }\n\n            @Override\n            public Class<InputStream> getDataClass() {\n              return InputStream.class;\n            }\n          };\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Model, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new DataUrlLoader<>(opener);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/DirectResourceLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.content.Context;\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.Resources;\nimport android.content.res.Resources.Theme;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Build.VERSION_CODES;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.resource.drawable.DrawableDecoderCompat;\nimport com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Loads themed resource ids using {@link Resources#openRawResource(int)} or {@link\n * Resources#openRawResourceFd(int)} using the theme from {@link ResourceDrawableDecoder#THEME} when\n * it's available.\n *\n * <p>Resource ids from other packages are handled by {@link ResourceLoader} via {@link\n * ResourceDrawableDecoder} and {@link\n * com.bumptech.glide.load.resource.bitmap.ResourceBitmapDecoder}.\n *\n * @param <DataT> The type of data this {@code ModelLoader} will produce (e.g. {@link InputStream},\n *     {@link AssetFileDescriptor} etc).\n */\npublic final class DirectResourceLoader<DataT> implements ModelLoader<Integer, DataT> {\n\n  private final Context context;\n  private final ResourceOpener<DataT> resourceOpener;\n\n  public static ModelLoaderFactory<Integer, InputStream> inputStreamFactory(Context context) {\n    return new InputStreamFactory(context);\n  }\n\n  public static ModelLoaderFactory<Integer, AssetFileDescriptor> assetFileDescriptorFactory(\n      Context context) {\n    return new AssetFileDescriptorFactory(context);\n  }\n\n  public static ModelLoaderFactory<Integer, Drawable> drawableFactory(Context context) {\n    return new DrawableFactory(context);\n  }\n\n  DirectResourceLoader(Context context, ResourceOpener<DataT> resourceOpener) {\n    this.context = context.getApplicationContext();\n    this.resourceOpener = resourceOpener;\n  }\n\n  @Override\n  public LoadData<DataT> buildLoadData(\n      @NonNull Integer resourceId, int width, int height, @NonNull Options options) {\n    Theme theme = options.get(ResourceDrawableDecoder.THEME);\n    Resources resources =\n        Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && theme != null\n            ? theme.getResources()\n            : context.getResources();\n    return new LoadData<>(\n        // TODO(judds): We try to apply AndroidResourceSignature for caching in RequestBuilder.\n        //  Arguably we should mix in that information here instead.\n        new ObjectKey(resourceId),\n        new ResourceDataFetcher<>(theme, resources, resourceOpener, resourceId));\n  }\n\n  @Override\n  public boolean handles(@NonNull Integer integer) {\n    // We could check that this is in fact a resource ID, but doing so isn't free and in practice\n    // it doesn't seem to have been an issue historically.\n    return true;\n  }\n\n  private interface ResourceOpener<DataT> {\n\n    /**\n     * {@code resources} is expected to come from the given {@code theme}, so {@code theme} does not\n     * need to be used if it's not required.\n     */\n    DataT open(@Nullable Theme theme, Resources resources, int resourceId);\n\n    void close(DataT data) throws IOException;\n\n    Class<DataT> getDataClass();\n  }\n\n  private static final class AssetFileDescriptorFactory\n      implements ModelLoaderFactory<Integer, AssetFileDescriptor>,\n          ResourceOpener<AssetFileDescriptor> {\n\n    private final Context context;\n\n    AssetFileDescriptorFactory(Context context) {\n      this.context = context;\n    }\n\n    @Override\n    public AssetFileDescriptor open(@Nullable Theme theme, Resources resources, int resourceId) {\n      return resources.openRawResourceFd(resourceId);\n    }\n\n    @Override\n    public void close(AssetFileDescriptor data) throws IOException {\n      data.close();\n    }\n\n    @Override\n    public Class<AssetFileDescriptor> getDataClass() {\n      return AssetFileDescriptor.class;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Integer, AssetFileDescriptor> build(\n        @NonNull MultiModelLoaderFactory multiFactory) {\n      return new DirectResourceLoader<>(context, this);\n    }\n\n    @Override\n    public void teardown() {}\n  }\n\n  private static final class InputStreamFactory\n      implements ModelLoaderFactory<Integer, InputStream>, ResourceOpener<InputStream> {\n\n    private final Context context;\n\n    InputStreamFactory(Context context) {\n      this.context = context;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Integer, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new DirectResourceLoader<>(context, this);\n    }\n\n    @Override\n    public InputStream open(@Nullable Theme theme, Resources resources, int resourceId) {\n      return resources.openRawResource(resourceId);\n    }\n\n    @Override\n    public void close(InputStream data) throws IOException {\n      data.close();\n    }\n\n    @Override\n    public Class<InputStream> getDataClass() {\n      return InputStream.class;\n    }\n\n    @Override\n    public void teardown() {}\n  }\n\n  /**\n   * Handles vectors, shapes and other resources that cannot be opened with\n   * Resources.openRawResource. Overlaps in functionality with {@link ResourceDrawableDecoder} and\n   * {@link com.bumptech.glide.load.resource.bitmap.ResourceBitmapDecoder} but it's more efficient\n   * for simple resource loads within a single application.\n   */\n  private static final class DrawableFactory\n      implements ModelLoaderFactory<Integer, Drawable>, ResourceOpener<Drawable> {\n\n    private final Context context;\n\n    DrawableFactory(Context context) {\n      this.context = context;\n    }\n\n    @Override\n    public Drawable open(@Nullable Theme theme, Resources resources, int resourceId) {\n      // The Resources already includes the theme provided with the request, so we don't need to\n      // provide the theme separately.\n      return DrawableDecoderCompat.getDrawable(context, resourceId, theme);\n    }\n\n    @Override\n    public void close(Drawable data) throws IOException {}\n\n    @Override\n    public Class<Drawable> getDataClass() {\n      return Drawable.class;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Integer, Drawable> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new DirectResourceLoader<>(context, this);\n    }\n\n    @Override\n    public void teardown() {}\n  }\n\n  private static final class ResourceDataFetcher<DataT> implements DataFetcher<DataT> {\n\n    @Nullable private final Theme theme;\n    private final Resources resources;\n    private final ResourceOpener<DataT> resourceOpener;\n    private final int resourceId;\n    @Nullable private DataT data;\n\n    ResourceDataFetcher(\n        @Nullable Theme theme,\n        Resources resources,\n        ResourceOpener<DataT> resourceOpener,\n        int resourceId) {\n      this.theme = theme;\n      this.resources = resources;\n      this.resourceOpener = resourceOpener;\n      this.resourceId = resourceId;\n    }\n\n    @Override\n    public void loadData(\n        @NonNull Priority priority, @NonNull DataCallback<? super DataT> callback) {\n      try {\n        data = resourceOpener.open(theme, resources, resourceId);\n        callback.onDataReady(data);\n      } catch (Resources.NotFoundException e) {\n        callback.onLoadFailed(e);\n      }\n    }\n\n    @Override\n    public void cleanup() {\n      DataT local = data;\n      if (local != null) {\n        try {\n          resourceOpener.close(local);\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n    }\n\n    @Override\n    public void cancel() {}\n\n    @NonNull\n    @Override\n    public Class<DataT> getDataClass() {\n      return resourceOpener.getDataClass();\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.LOCAL;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/FileLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.os.ParcelFileDescriptor;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * A simple model loader for loading data from {@link File}s.\n *\n * @param <Data> The type of data loaded from the given {@link java.io.File} ({@link\n *     java.io.InputStream} or {@link java.io.FileDescriptor} etc).\n */\npublic class FileLoader<Data> implements ModelLoader<File, Data> {\n  private static final String TAG = \"FileLoader\";\n\n  private final FileOpener<Data> fileOpener;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public FileLoader(FileOpener<Data> fileOpener) {\n    this.fileOpener = fileOpener;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull File model, int width, int height, @NonNull Options options) {\n    return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener));\n  }\n\n  @Override\n  public boolean handles(@NonNull File model) {\n    return true;\n  }\n\n  /**\n   * Allows opening a specific type of data from a {@link java.io.File}.\n   *\n   * @param <Data> The type of data that can be opened.\n   */\n  public interface FileOpener<Data> {\n    Data open(File file) throws FileNotFoundException;\n\n    void close(Data data) throws IOException;\n\n    Class<Data> getDataClass();\n  }\n\n  private static final class FileFetcher<Data> implements DataFetcher<Data> {\n    private final File file;\n    private final FileOpener<Data> opener;\n    private Data data;\n\n    FileFetcher(File file, FileOpener<Data> opener) {\n      this.file = file;\n      this.opener = opener;\n    }\n\n    @Override\n    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {\n      try {\n        data = opener.open(file);\n        callback.onDataReady(data);\n      } catch (FileNotFoundException e) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Failed to open file\", e);\n        }\n        callback.onLoadFailed(e);\n      }\n    }\n\n    @Override\n    public void cleanup() {\n      if (data != null) {\n        try {\n          opener.close(data);\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n    }\n\n    @Override\n    public void cancel() {\n      // Do nothing.\n    }\n\n    @NonNull\n    @Override\n    public Class<Data> getDataClass() {\n      return opener.getDataClass();\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.LOCAL;\n    }\n  }\n\n  /**\n   * Base factory for loading data from {@link java.io.File files}.\n   *\n   * @param <Data> The type of data that will be loaded for a given {@link java.io.File}.\n   */\n  public static class Factory<Data> implements ModelLoaderFactory<File, Data> {\n    private final FileOpener<Data> opener;\n\n    public Factory(FileOpener<Data> opener) {\n      this.opener = opener;\n    }\n\n    @NonNull\n    @Override\n    public final ModelLoader<File, Data> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new FileLoader<>(opener);\n    }\n\n    @Override\n    public final void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /** Factory for loading {@link InputStream}s from {@link File}s. */\n  public static class StreamFactory extends Factory<InputStream> {\n    public StreamFactory() {\n      super(\n          new FileOpener<InputStream>() {\n            @Override\n            public InputStream open(File file) throws FileNotFoundException {\n              return new FileInputStream(file);\n            }\n\n            @Override\n            public void close(InputStream inputStream) throws IOException {\n              inputStream.close();\n            }\n\n            @Override\n            public Class<InputStream> getDataClass() {\n              return InputStream.class;\n            }\n          });\n    }\n  }\n\n  /** Factory for loading {@link ParcelFileDescriptor}s from {@link File}s. */\n  public static class FileDescriptorFactory extends Factory<ParcelFileDescriptor> {\n\n    public FileDescriptorFactory() {\n      super(\n          new FileOpener<ParcelFileDescriptor>() {\n            @Override\n            public ParcelFileDescriptor open(File file) throws FileNotFoundException {\n              return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);\n            }\n\n            @Override\n            public void close(ParcelFileDescriptor parcelFileDescriptor) throws IOException {\n              parcelFileDescriptor.close();\n            }\n\n            @Override\n            public Class<ParcelFileDescriptor> getDataClass() {\n              return ParcelFileDescriptor.class;\n            }\n          });\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/GlideUrl.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.net.Uri;\nimport android.text.TextUtils;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.util.Preconditions;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.security.MessageDigest;\nimport java.util.Map;\n\n/**\n * A wrapper for strings representing http/https URLs responsible for ensuring URLs are properly\n * escaped and avoiding unnecessary URL instantiations for loaders that require only string urls\n * rather than URL objects.\n *\n * <p>Users wishing to replace the class for handling URLs must register a factory using GlideUrl.\n *\n * <p>To obtain a properly escaped URL, call {@link #toURL()}. To obtain a properly escaped string\n * URL, call {@link #toStringUrl()}. To obtain a less safe, but less expensive to calculate cache\n * key, call {@link #getCacheKey()}.\n *\n * <p>This class can also optionally wrap {@link com.bumptech.glide.load.model.Headers} for\n * convenience.\n */\npublic class GlideUrl implements Key {\n  private static final String ALLOWED_URI_CHARS = \"@#&=*+-_.,:!?()/~'%;$[]\";\n  private final Headers headers;\n  @Nullable private final URL url;\n  @Nullable private final String stringUrl;\n\n  @Nullable private String safeStringUrl;\n  @Nullable private URL safeUrl;\n  @Nullable private volatile byte[] cacheKeyBytes;\n\n  private int hashCode;\n\n  public GlideUrl(URL url) {\n    this(url, Headers.DEFAULT);\n  }\n\n  public GlideUrl(String url) {\n    this(url, Headers.DEFAULT);\n  }\n\n  public GlideUrl(URL url, Headers headers) {\n    this.url = Preconditions.checkNotNull(url);\n    stringUrl = null;\n    this.headers = Preconditions.checkNotNull(headers);\n  }\n\n  public GlideUrl(String url, Headers headers) {\n    this.url = null;\n    this.stringUrl = Preconditions.checkNotEmpty(url);\n    this.headers = Preconditions.checkNotNull(headers);\n  }\n\n  public URL toURL() throws MalformedURLException {\n    return getSafeUrl();\n  }\n\n  // See http://stackoverflow.com/questions/3286067/url-encoding-in-android. Although the answer\n  // using URI would work, using it would require both decoding and encoding each string which is\n  // more complicated, slower and generates more objects than the solution below. See also issue\n  // #133.\n  private URL getSafeUrl() throws MalformedURLException {\n    if (safeUrl == null) {\n      safeUrl = new URL(getSafeStringUrl());\n    }\n    return safeUrl;\n  }\n\n  /**\n   * Returns a properly escaped {@link String} url that can be used to make http/https requests.\n   *\n   * @see #toURL()\n   * @see #getCacheKey()\n   */\n  public String toStringUrl() {\n    return getSafeStringUrl();\n  }\n\n  private String getSafeStringUrl() {\n    if (TextUtils.isEmpty(safeStringUrl)) {\n      String unsafeStringUrl = stringUrl;\n      if (TextUtils.isEmpty(unsafeStringUrl)) {\n        unsafeStringUrl = Preconditions.checkNotNull(url).toString();\n      }\n      safeStringUrl = Uri.encode(unsafeStringUrl, ALLOWED_URI_CHARS);\n    }\n    return safeStringUrl;\n  }\n\n  /** Returns a non-null {@link Map} containing headers. */\n  public Map<String, String> getHeaders() {\n    return headers.getHeaders();\n  }\n\n  /**\n   * Returns an inexpensive to calculate {@link String} suitable for use as a disk cache key.\n   *\n   * <p>This method does not include headers.\n   *\n   * <p>Unlike {@link #toStringUrl()}} and {@link #toURL()}, this method does not escape input.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public String getCacheKey() {\n    return stringUrl != null ? stringUrl : Preconditions.checkNotNull(url).toString();\n  }\n\n  @Override\n  public String toString() {\n    return getCacheKey();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(getCacheKeyBytes());\n  }\n\n  private byte[] getCacheKeyBytes() {\n    if (cacheKeyBytes == null) {\n      cacheKeyBytes = getCacheKey().getBytes(CHARSET);\n    }\n    return cacheKeyBytes;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof GlideUrl) {\n      GlideUrl other = (GlideUrl) o;\n      return getCacheKey().equals(other.getCacheKey()) && headers.equals(other.headers);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    if (hashCode == 0) {\n      hashCode = getCacheKey().hashCode();\n      hashCode = 31 * hashCode + headers.hashCode();\n    }\n    return hashCode;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/Headers.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n * An interface for a wrapper for a set of headers to be included in a Glide request.\n *\n * <p>Implementations must implement equals() and hashcode().\n */\npublic interface Headers {\n\n  /**\n   * An empty Headers object that can be used if users don't want to provide headers.\n   *\n   * @deprecated Use {@link #DEFAULT} instead.\n   */\n  @Deprecated\n  Headers NONE =\n      new Headers() {\n        @Override\n        public Map<String, String> getHeaders() {\n          return Collections.emptyMap();\n        }\n      };\n\n  /**\n   * A Headers object containing reasonable defaults that should be used when users don't want to\n   * provide their own headers.\n   */\n  Headers DEFAULT = new LazyHeaders.Builder().build();\n\n  /** Returns a non-null map containing a set of headers to apply to an http request. */\n  Map<String, String> getHeaders();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/LazyHeaderFactory.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.Nullable;\n\n/**\n * An interface for lazily creating headers that allows expensive to calculate headers (oauth for\n * example) to be generated in the background during the first fetch.\n *\n * <p>Implementations should implement equals() and hashcode() .\n */\npublic interface LazyHeaderFactory {\n  /**\n   * Returns an http header, or {@code null} if no header could be built.\n   *\n   * <p>Returning {@code null} or an empty String from this method will result in this particular\n   * key/value being excluded from the headers provided in the request. If there are multiple\n   * factories or values for a particular key, any non-null values will still be included for that\n   * key.\n   */\n  @Nullable\n  String buildHeader();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/LazyHeaders.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.text.TextUtils;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * A wrapper class for a set of headers to be included in a Glide request, allowing headers to be\n * constructed lazily.\n *\n * <p>Ideally headers are constructed once and then re-used for multiple loads, rather then being\n * constructed individually for each load.\n *\n * <p>This class is thread safe.\n */\npublic final class LazyHeaders implements Headers {\n  private final Map<String, List<LazyHeaderFactory>> headers;\n  private volatile Map<String, String> combinedHeaders;\n\n  LazyHeaders(Map<String, List<LazyHeaderFactory>> headers) {\n    this.headers = Collections.unmodifiableMap(headers);\n  }\n\n  @Override\n  public Map<String, String> getHeaders() {\n    if (combinedHeaders == null) {\n      synchronized (this) {\n        if (combinedHeaders == null) {\n          this.combinedHeaders = Collections.unmodifiableMap(generateHeaders());\n        }\n      }\n    }\n\n    return combinedHeaders;\n  }\n\n  private Map<String, String> generateHeaders() {\n    Map<String, String> combinedHeaders = new HashMap<>();\n\n    for (Map.Entry<String, List<LazyHeaderFactory>> entry : headers.entrySet()) {\n      String values = buildHeaderValue(entry.getValue());\n      if (!TextUtils.isEmpty(values)) {\n        combinedHeaders.put(entry.getKey(), values);\n      }\n    }\n\n    return combinedHeaders;\n  }\n\n  @NonNull\n  private String buildHeaderValue(@NonNull List<LazyHeaderFactory> factories) {\n    StringBuilder sb = new StringBuilder();\n    int size = factories.size();\n    for (int i = 0; i < size; i++) {\n      LazyHeaderFactory factory = factories.get(i);\n      String header = factory.buildHeader();\n      if (!TextUtils.isEmpty(header)) {\n        sb.append(header);\n        if (i != factories.size() - 1) {\n          sb.append(',');\n        }\n      }\n    }\n    return sb.toString();\n  }\n\n  @Override\n  public String toString() {\n    return \"LazyHeaders{\" + \"headers=\" + headers + '}';\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof LazyHeaders) {\n      LazyHeaders other = (LazyHeaders) o;\n      return headers.equals(other.headers);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return headers.hashCode();\n  }\n\n  /**\n   * Adds an {@link LazyHeaderFactory} that will be used to construct a value for the given key*\n   * lazily on a background thread.\n   *\n   * <p>This class is not thread safe.\n   *\n   * <p>This class may include default values for User-Agent and Accept-Encoding headers. These will\n   * be replaced by calls to either {@link #setHeader(String, LazyHeaderFactory)} or {@link\n   * #addHeader(String, String)}, even though {@link #addHeader(String, LazyHeaderFactory)} would\n   * usually append an additional value.\n   */\n  public static final class Builder {\n    private static final String USER_AGENT_HEADER = \"User-Agent\";\n    private static final String DEFAULT_USER_AGENT = getSanitizedUserAgent();\n    private static final Map<String, List<LazyHeaderFactory>> DEFAULT_HEADERS;\n\n    // Set Accept-Encoding header to do our best to avoid gzip since it's both inefficient for\n    // images and also makes it more difficult for us to detect and prevent partial content\n    // rendering. See #440.\n    static {\n      Map<String, List<LazyHeaderFactory>> temp = new HashMap<>(2);\n      if (!TextUtils.isEmpty(DEFAULT_USER_AGENT)) {\n        temp.put(\n            USER_AGENT_HEADER,\n            Collections.<LazyHeaderFactory>singletonList(\n                new StringHeaderFactory(DEFAULT_USER_AGENT)));\n      }\n      DEFAULT_HEADERS = Collections.unmodifiableMap(temp);\n    }\n\n    private boolean copyOnModify = true;\n    private Map<String, List<LazyHeaderFactory>> headers = DEFAULT_HEADERS;\n    private boolean isUserAgentDefault = true;\n\n    /**\n     * Adds a value for the given header and returns this builder.\n     *\n     * <p>Use {@link #addHeader(String, LazyHeaderFactory)} if obtaining the value requires I/O\n     * (i.e. an OAuth token).\n     *\n     * @see #addHeader(String, LazyHeaderFactory)\n     */\n    public Builder addHeader(@NonNull String key, @NonNull String value) {\n      return addHeader(key, new StringHeaderFactory(value));\n    }\n\n    /**\n     * Adds an {@link LazyHeaderFactory} that will be used to construct a value for the given key\n     * lazily on a background thread.\n     *\n     * <p>Headers may have multiple values whose order is defined by the order in which this method\n     * is called.\n     *\n     * <p>This class does not prevent you from adding the same value to a given key multiple times\n     */\n    public Builder addHeader(@NonNull String key, @NonNull LazyHeaderFactory factory) {\n      if (isUserAgentDefault && USER_AGENT_HEADER.equalsIgnoreCase(key)) {\n        return setHeader(key, factory);\n      }\n\n      copyIfNecessary();\n      getFactories(key).add(factory);\n      return this;\n    }\n\n    /**\n     * Replaces all existing {@link LazyHeaderFactory LazyHeaderFactorys} for the given key with the\n     * given {@link LazyHeaderFactory}.\n     *\n     * <p>If the given value is {@code null}, the header at the given key will be removed.\n     *\n     * <p>Use {@link #setHeader(String, LazyHeaderFactory)} if obtaining the value requires I/O\n     * (i.e. an OAuth token).\n     */\n    @SuppressWarnings({\"UnusedReturnValue\", \"WeakerAccess\"}) // Public API\n    public Builder setHeader(@NonNull String key, @Nullable String value) {\n      return setHeader(key, value == null ? null : new StringHeaderFactory(value));\n    }\n\n    /**\n     * Replaces all existing {@link LazyHeaderFactory LazyHeaderFactorys} for the given key with the\n     * given {@link LazyHeaderFactory}.\n     *\n     * <p>If the given value is {@code null}, the header at the given key will be removed.\n     */\n    public Builder setHeader(@NonNull String key, @Nullable LazyHeaderFactory factory) {\n      copyIfNecessary();\n      if (factory == null) {\n        headers.remove(key);\n      } else {\n        List<LazyHeaderFactory> factories = getFactories(key);\n        factories.clear();\n        factories.add(factory);\n      }\n\n      if (isUserAgentDefault && USER_AGENT_HEADER.equalsIgnoreCase(key)) {\n        isUserAgentDefault = false;\n      }\n\n      return this;\n    }\n\n    private List<LazyHeaderFactory> getFactories(String key) {\n      List<LazyHeaderFactory> factories = headers.get(key);\n      if (factories == null) {\n        factories = new ArrayList<>();\n        headers.put(key, factories);\n      }\n      return factories;\n    }\n\n    private void copyIfNecessary() {\n      if (copyOnModify) {\n        copyOnModify = false;\n        headers = copyHeaders();\n      }\n    }\n\n    /** Returns a new immutable {@link LazyHeaders} object. */\n    public LazyHeaders build() {\n      copyOnModify = true;\n      return new LazyHeaders(headers);\n    }\n\n    private Map<String, List<LazyHeaderFactory>> copyHeaders() {\n      Map<String, List<LazyHeaderFactory>> result = new HashMap<>(headers.size());\n      for (Map.Entry<String, List<LazyHeaderFactory>> entry : headers.entrySet()) {\n        @SuppressWarnings(\"PMD.AvoidInstantiatingObjectsInLoops\")\n        List<LazyHeaderFactory> valueCopy = new ArrayList<>(entry.getValue());\n        result.put(entry.getKey(), valueCopy);\n      }\n      return result;\n    }\n\n    /**\n     * Ensures that the default header will pass OkHttp3's checks for header values.\n     *\n     * @see <a href=\"https://github.com/bumptech/glide/issues/2331\">#2331</a>\n     */\n    @VisibleForTesting\n    static String getSanitizedUserAgent() {\n      String defaultUserAgent = System.getProperty(\"http.agent\");\n      if (TextUtils.isEmpty(defaultUserAgent)) {\n        return defaultUserAgent;\n      }\n\n      int length = defaultUserAgent.length();\n      StringBuilder sb = new StringBuilder(defaultUserAgent.length());\n      for (int i = 0; i < length; i++) {\n        char c = defaultUserAgent.charAt(i);\n        if ((c > '\\u001f' || c == '\\t') && c < '\\u007f') {\n          sb.append(c);\n        } else {\n          sb.append('?');\n        }\n      }\n      return sb.toString();\n    }\n  }\n\n  static final class StringHeaderFactory implements LazyHeaderFactory {\n\n    @NonNull private final String value;\n\n    StringHeaderFactory(@NonNull String value) {\n      this.value = value;\n    }\n\n    @Override\n    public String buildHeader() {\n      return value;\n    }\n\n    @Override\n    public String toString() {\n      return \"StringHeaderFactory{\" + \"value='\" + value + '\\'' + '}';\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof StringHeaderFactory) {\n        StringHeaderFactory other = (StringHeaderFactory) o;\n        return value.equals(other.value);\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      return value.hashCode();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/MediaStoreFileLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.MediaStore;\nimport android.text.TextUtils;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.mediastore.MediaStoreUtil;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.File;\nimport java.io.FileNotFoundException;\n\n/** Loads the file path for {@link MediaStore} owned {@link Uri uris}. */\npublic final class MediaStoreFileLoader implements ModelLoader<Uri, File> {\n\n  private final Context context;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public MediaStoreFileLoader(Context context) {\n    this.context = context;\n  }\n\n  @Override\n  public LoadData<File> buildLoadData(\n      @NonNull Uri uri, int width, int height, @NonNull Options options) {\n    return new LoadData<>(new ObjectKey(uri), new FilePathFetcher(context, uri));\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri uri) {\n    return MediaStoreUtil.isMediaStoreUri(uri);\n  }\n\n  private static class FilePathFetcher implements DataFetcher<File> {\n    private static final String[] PROJECTION =\n        new String[] {\n          MediaStore.MediaColumns.DATA,\n        };\n\n    private final Context context;\n    private final Uri uri;\n\n    FilePathFetcher(Context context, Uri uri) {\n      this.context = context;\n      this.uri = uri;\n    }\n\n    @Override\n    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super File> callback) {\n      Cursor cursor =\n          context\n              .getContentResolver()\n              .query(\n                  uri, PROJECTION, null /*selection*/, null /*selectionArgs*/, null /*sortOrder*/);\n\n      String filePath = null;\n      if (cursor != null) {\n        try {\n          if (cursor.moveToFirst()) {\n            filePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA));\n          }\n        } finally {\n          cursor.close();\n        }\n      }\n\n      if (TextUtils.isEmpty(filePath)) {\n        callback.onLoadFailed(new FileNotFoundException(\"Failed to find file path for: \" + uri));\n      } else {\n        callback.onDataReady(new File(filePath));\n      }\n    }\n\n    @Override\n    public void cleanup() {\n      // Do nothing.\n    }\n\n    @Override\n    public void cancel() {\n      // Do nothing.\n    }\n\n    @NonNull\n    @Override\n    public Class<File> getDataClass() {\n      return File.class;\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.LOCAL;\n    }\n  }\n\n  /** {@link ModelLoaderFactory} for {@link MediaStoreFileLoader}s. */\n  public static final class Factory implements ModelLoaderFactory<Uri, File> {\n\n    private final Context context;\n\n    public Factory(Context context) {\n      this.context = context;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, File> build(MultiModelLoaderFactory multiFactory) {\n      return new MediaStoreFileLoader(context);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/Model.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.Nullable;\n\n/** An optional interface that models can implement to enhance control over Glide behaviors. */\npublic interface Model {\n\n  /**\n   * Returns {@code true} if this model produces the same image using the same mechanism (server,\n   * authentication, source etc) as the given model.\n   *\n   * <p>Models must also override {@link Object#equals(Object other)} and {@link Object#hashCode()}\n   * to ensure that caching functions correctly. If this object returns {@code true} from this\n   * method for a given Model, it must also be equal to and have the same hash code as the given\n   * model.\n   *\n   * <p>However, this model may be equal to and have the same hash code as a given model but still\n   * return {@code false} from this method. This method optionally allows you to differentiate\n   * between Models that load the same image via multiple different means. For example one Model\n   * might load the image from server A and another model might load the same image from server B.\n   * The models must be equal to each other with the same hash code because they load the same\n   * image. However two requests made with the different models are not exactly the same because the\n   * way the image is loaded will differ.\n   */\n  boolean isEquivalentTo(@Nullable Object other);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ModelCache.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.LruCache;\nimport com.bumptech.glide.util.Util;\nimport java.util.Queue;\n\n/**\n * A simple cache that can be used by {@link ModelLoader} and {@link ModelLoaderFactory} to cache\n * some data for a given model, width and height. For a loader that takes a model and returns a url,\n * the cache could be used to safely memoize url creation based on the width and height of the view.\n *\n * @param <A> Some Model type that implements {@link #equals} and {@link #hashCode}.\n * @param <B> Some useful type that may be expensive to create (URL, file path, etc).\n */\npublic class ModelCache<A, B> {\n  private static final int DEFAULT_SIZE = 250;\n\n  private final LruCache<ModelKey<A>, B> cache;\n\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public ModelCache() {\n    this(DEFAULT_SIZE);\n  }\n\n  public ModelCache(long size) {\n    cache =\n        new LruCache<ModelKey<A>, B>(size) {\n          @Override\n          protected void onItemEvicted(@NonNull ModelKey<A> key, @Nullable B item) {\n            key.release();\n          }\n        };\n  }\n\n  /**\n   * Get a value.\n   *\n   * @param model The model.\n   * @param width The width in pixels of the view the image is being loaded into.\n   * @param height The height in pixels of the view the image is being loaded into.\n   * @return The cached result, or null.\n   */\n  @Nullable\n  public B get(A model, int width, int height) {\n    ModelKey<A> key = ModelKey.get(model, width, height);\n    B result = cache.get(key);\n    key.release();\n    return result;\n  }\n\n  /**\n   * Add a value.\n   *\n   * @param model The model.\n   * @param width The width in pixels of the view the image is being loaded into.\n   * @param height The height in pixels of the view the image is being loaded into.\n   * @param value The value to store.\n   */\n  public void put(A model, int width, int height, B value) {\n    ModelKey<A> key = ModelKey.get(model, width, height);\n    cache.put(key, value);\n  }\n\n  /** Removes all entries from the cache. */\n  public void clear() {\n    cache.clearMemory();\n  }\n\n  @VisibleForTesting\n  static final class ModelKey<A> {\n    private static final Queue<ModelKey<?>> KEY_QUEUE = Util.createQueue(0);\n\n    private int height;\n    private int width;\n    private A model;\n\n    @SuppressWarnings(\"unchecked\")\n    static <A> ModelKey<A> get(A model, int width, int height) {\n      ModelKey<A> modelKey;\n      synchronized (KEY_QUEUE) {\n        modelKey = (ModelKey<A>) KEY_QUEUE.poll();\n      }\n      if (modelKey == null) {\n        modelKey = new ModelKey<>();\n      }\n\n      modelKey.init(model, width, height);\n      return modelKey;\n    }\n\n    private ModelKey() {}\n\n    private void init(A model, int width, int height) {\n      this.model = model;\n      this.width = width;\n      this.height = height;\n    }\n\n    public void release() {\n      synchronized (KEY_QUEUE) {\n        KEY_QUEUE.offer(this);\n      }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof ModelKey) {\n        @SuppressWarnings(\"unchecked\")\n        ModelKey<A> other = (ModelKey<A>) o;\n        return width == other.width && height == other.height && model.equals(other.model);\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      int result = height;\n      result = 31 * result + width;\n      result = 31 * result + model.hashCode();\n      return result;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ModelLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * A factory interface for translating an arbitrarily complex data model into a concrete data type\n * that can be used by an {@link DataFetcher} to obtain the data for a resource represented by the\n * model.\n *\n * <p>This interface has two objectives: 1. To translate a specific model into a data type that can\n * be decoded into a resource.\n *\n * <p>2. To allow a model to be combined with the dimensions of the view to fetch a resource of a\n * specific size.\n *\n * <p>This not only avoids having to duplicate dimensions in xml and in your code in order to\n * determine the size of a view on devices with different densities, but also allows you to use\n * layout weights or otherwise programmatically put the dimensions of the view without forcing you\n * to fetch a generic resource size.\n *\n * <p>The smaller the resource you fetch, the less bandwidth and battery life you use, and the lower\n * your memory footprint per resource.\n *\n * @param <Model> The type of the model.\n * @param <Data> The type of the data that can be used by a {@link\n *     com.bumptech.glide.load.ResourceDecoder} to decode a resource.\n */\npublic interface ModelLoader<Model, Data> {\n\n  /**\n   * Contains a set of {@link com.bumptech.glide.load.Key Keys} identifying the source of the load,\n   * alternate cache keys pointing to equivalent data, and a {@link\n   * com.bumptech.glide.load.data.DataFetcher} that can be used to fetch data not found in cache.\n   *\n   * @param <Data> The type of data that well be loaded.\n   */\n  class LoadData<Data> {\n    public final Key sourceKey;\n    public final List<Key> alternateKeys;\n    public final DataFetcher<Data> fetcher;\n\n    public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher<Data> fetcher) {\n      this(sourceKey, Collections.<Key>emptyList(), fetcher);\n    }\n\n    public LoadData(\n        @NonNull Key sourceKey,\n        @NonNull List<Key> alternateKeys,\n        @NonNull DataFetcher<Data> fetcher) {\n      this.sourceKey = Preconditions.checkNotNull(sourceKey);\n      this.alternateKeys = Preconditions.checkNotNull(alternateKeys);\n      this.fetcher = Preconditions.checkNotNull(fetcher);\n    }\n  }\n\n  /**\n   * Returns a {@link com.bumptech.glide.load.model.ModelLoader.LoadData} containing a {@link\n   * com.bumptech.glide.load.data.DataFetcher} required to decode the resource represented by this\n   * model, as well as a set of {@link com.bumptech.glide.load.Key Keys} that identify the data\n   * loaded by the {@link com.bumptech.glide.load.data.DataFetcher} as well as an optional list of\n   * alternate keys from which equivalent data can be loaded. The {@link DataFetcher} will not be\n   * used if the resource is already cached.\n   *\n   * <p>Note - If no valid data fetcher can be returned (for example if a model has a null URL),\n   * then it is acceptable to return a null data fetcher from this method.\n   *\n   * @param model The model representing the resource.\n   * @param width The width in pixels of the view or target the resource will be loaded into, or\n   *     {@link com.bumptech.glide.request.target.Target#SIZE_ORIGINAL} to indicate that the\n   *     resource should be loaded at its original width.\n   * @param height The height in pixels of the view or target the resource will be loaded into, or\n   *     {@link com.bumptech.glide.request.target.Target#SIZE_ORIGINAL} to indicate that the\n   *     resource should be loaded at its original height.\n   */\n  @Nullable\n  LoadData<Data> buildLoadData(\n      @NonNull Model model, int width, int height, @NonNull Options options);\n\n  /**\n   * Returns true if the given model is a of a recognized type that this loader can probably load.\n   *\n   * <p>For example, you may want multiple Uri to InputStream loaders. One might handle media store\n   * Uris, another might handle asset Uris, and a third might handle file Uris etc.\n   *\n   * <p>This method is generally expected to do no I/O and complete quickly, so best effort results\n   * are acceptable. {@link ModelLoader ModelLoaders} that return true from this method may return\n   * {@code null} from {@link #buildLoadData(Object, int, int, Options)}\n   */\n  boolean handles(@NonNull Model model);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ModelLoaderFactory.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\n\n/**\n * An interface for creating a {@link ModelLoader} for a given model type.\n *\n * <p>The application {@link android.content.Context} can be passed in to the constructor of the\n * factory when necessary. It's unsafe to retain {@link android.app.Activity} {@link\n * android.content.Context}s in factories. The {@link android.content.Context} can be obtained from\n * {@link com.bumptech.glide.module.LibraryGlideModule#registerComponents(Context, Glide, Registry)}\n * in most cases.\n *\n * @param <T> The type of the model the {@link com.bumptech.glide.load.model.ModelLoader}s built by\n *     this factory can handle\n * @param <Y> The type of data the {@link com.bumptech.glide.load.model.ModelLoader}s built by this\n *     factory can load.\n */\npublic interface ModelLoaderFactory<T, Y> {\n\n  /**\n   * Build a concrete ModelLoader for this model type.\n   *\n   * @param multiFactory A map of classes to factories that can be used to construct additional\n   *     {@link ModelLoader}s that this factory's {@link ModelLoader} may depend on\n   * @return A new {@link ModelLoader}\n   */\n  @NonNull\n  ModelLoader<T, Y> build(@NonNull MultiModelLoaderFactory multiFactory);\n\n  /** A lifecycle method that will be called when this factory is about to replaced. */\n  void teardown();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ModelLoaderRegistry.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.util.Pools.Pool;\nimport com.bumptech.glide.Registry.NoModelLoaderAvailableException;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Maintains an ordered put of {@link ModelLoader}s and the model and data types they handle in\n * order from highest priority to lowest.\n */\n// Hides Model throughout.\n@SuppressWarnings(\"TypeParameterHidesVisibleType\")\npublic class ModelLoaderRegistry {\n\n  private final MultiModelLoaderFactory multiModelLoaderFactory;\n  private final ModelLoaderCache cache = new ModelLoaderCache();\n\n  public ModelLoaderRegistry(@NonNull Pool<List<Throwable>> throwableListPool) {\n    this(new MultiModelLoaderFactory(throwableListPool));\n  }\n\n  private ModelLoaderRegistry(@NonNull MultiModelLoaderFactory multiModelLoaderFactory) {\n    this.multiModelLoaderFactory = multiModelLoaderFactory;\n  }\n\n  public synchronized <Model, Data> void append(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {\n    multiModelLoaderFactory.append(modelClass, dataClass, factory);\n    cache.clear();\n  }\n\n  public synchronized <Model, Data> void prepend(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {\n    multiModelLoaderFactory.prepend(modelClass, dataClass, factory);\n    cache.clear();\n  }\n\n  public synchronized <Model, Data> void remove(\n      @NonNull Class<Model> modelClass, @NonNull Class<Data> dataClass) {\n    tearDown(multiModelLoaderFactory.remove(modelClass, dataClass));\n    cache.clear();\n  }\n\n  public synchronized <Model, Data> void replace(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {\n    tearDown(multiModelLoaderFactory.replace(modelClass, dataClass, factory));\n    cache.clear();\n  }\n\n  private <Model, Data> void tearDown(\n      @NonNull List<ModelLoaderFactory<? extends Model, ? extends Data>> factories) {\n    for (ModelLoaderFactory<? extends Model, ? extends Data> factory : factories) {\n      factory.teardown();\n    }\n  }\n\n  // We're allocating in a loop to avoid allocating empty lists that will never have anything added\n  // to them.\n  @SuppressWarnings(\"PMD.AvoidInstantiatingObjectsInLoops\")\n  @NonNull\n  public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {\n    List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));\n    if (modelLoaders.isEmpty()) {\n      throw new NoModelLoaderAvailableException(model);\n    }\n    int size = modelLoaders.size();\n    boolean isEmpty = true;\n    List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0; i < size; i++) {\n      ModelLoader<A, ?> loader = modelLoaders.get(i);\n      if (loader.handles(model)) {\n        if (isEmpty) {\n          filteredLoaders = new ArrayList<>(size - i);\n          isEmpty = false;\n        }\n        filteredLoaders.add(loader);\n      }\n    }\n    if (filteredLoaders.isEmpty()) {\n      throw new NoModelLoaderAvailableException(model, modelLoaders);\n    }\n    return filteredLoaders;\n  }\n\n  public synchronized <Model, Data> ModelLoader<Model, Data> build(\n      @NonNull Class<Model> modelClass, @NonNull Class<Data> dataClass) {\n    return multiModelLoaderFactory.build(modelClass, dataClass);\n  }\n\n  @NonNull\n  public synchronized List<Class<?>> getDataClasses(@NonNull Class<?> modelClass) {\n    return multiModelLoaderFactory.getDataClasses(modelClass);\n  }\n\n  @NonNull\n  private synchronized <A> List<ModelLoader<A, ?>> getModelLoadersForClass(\n      @NonNull Class<A> modelClass) {\n    List<ModelLoader<A, ?>> loaders = cache.get(modelClass);\n    if (loaders == null) {\n      loaders = Collections.unmodifiableList(multiModelLoaderFactory.build(modelClass));\n      cache.put(modelClass, loaders);\n    }\n    return loaders;\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  private static <A> Class<A> getClass(@NonNull A model) {\n    return (Class<A>) model.getClass();\n  }\n\n  private static class ModelLoaderCache {\n    private final Map<Class<?>, Entry<?>> cachedModelLoaders = new HashMap<>();\n\n    @Synthetic\n    ModelLoaderCache() {}\n\n    public void clear() {\n      cachedModelLoaders.clear();\n    }\n\n    public <Model> void put(Class<Model> modelClass, List<ModelLoader<Model, ?>> loaders) {\n      Entry<?> previous = cachedModelLoaders.put(modelClass, new Entry<>(loaders));\n      if (previous != null) {\n        throw new IllegalStateException(\"Already cached loaders for model: \" + modelClass);\n      }\n    }\n\n    @Nullable\n    @SuppressWarnings(\"unchecked\")\n    public <Model> List<ModelLoader<Model, ?>> get(Class<Model> modelClass) {\n      Entry<Model> entry = (Entry<Model>) cachedModelLoaders.get(modelClass);\n      return entry == null ? null : entry.loaders;\n    }\n\n    private static class Entry<Model> {\n      @Synthetic final List<ModelLoader<Model, ?>> loaders;\n\n      public Entry(List<ModelLoader<Model, ?>> loaders) {\n        this.loaders = loaders;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/MultiModelLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.util.Pools.Pool;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.DataFetcher.DataCallback;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Allows attempting multiple ModelLoaders registered for a given model and data class.\n *\n * <p>TODO: we should try to find a way to remove this class. It exists to allow individual\n * ModelLoaders to delegate to multiple ModelLoaders without having to duplicate this logic\n * everywhere. We have very similar logic in the {@link\n * com.bumptech.glide.load.engine.DataFetcherGenerator} implementations and should try to avoid this\n * duplication.\n */\nclass MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {\n\n  private final List<ModelLoader<Model, Data>> modelLoaders;\n  private final Pool<List<Throwable>> exceptionListPool;\n\n  MultiModelLoader(\n      @NonNull List<ModelLoader<Model, Data>> modelLoaders,\n      @NonNull Pool<List<Throwable>> exceptionListPool) {\n    this.modelLoaders = modelLoaders;\n    this.exceptionListPool = exceptionListPool;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull Model model, int width, int height, @NonNull Options options) {\n    Key sourceKey = null;\n    int size = modelLoaders.size();\n    List<DataFetcher<Data>> fetchers = new ArrayList<>(size);\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0; i < size; i++) {\n      ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);\n      if (modelLoader.handles(model)) {\n        LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options);\n        if (loadData != null) {\n          sourceKey = loadData.sourceKey;\n          fetchers.add(loadData.fetcher);\n        }\n      }\n    }\n    return !fetchers.isEmpty() && sourceKey != null\n        ? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool))\n        : null;\n  }\n\n  @Override\n  public boolean handles(@NonNull Model model) {\n    for (ModelLoader<Model, Data> modelLoader : modelLoaders) {\n      if (modelLoader.handles(model)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  @Override\n  public String toString() {\n    return \"MultiModelLoader{\" + \"modelLoaders=\" + Arrays.toString(modelLoaders.toArray()) + '}';\n  }\n\n  static class MultiFetcher<Data> implements DataFetcher<Data>, DataCallback<Data> {\n\n    private final List<DataFetcher<Data>> fetchers;\n    private final Pool<List<Throwable>> throwableListPool;\n    private int currentIndex;\n    private Priority priority;\n    private DataCallback<? super Data> callback;\n    @Nullable private List<Throwable> exceptions;\n    private boolean isCancelled;\n\n    MultiFetcher(\n        @NonNull List<DataFetcher<Data>> fetchers,\n        @NonNull Pool<List<Throwable>> throwableListPool) {\n      this.throwableListPool = throwableListPool;\n      Preconditions.checkNotEmpty(fetchers);\n      this.fetchers = fetchers;\n      currentIndex = 0;\n    }\n\n    @Override\n    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {\n      this.priority = priority;\n      this.callback = callback;\n      exceptions = throwableListPool.acquire();\n      fetchers.get(currentIndex).loadData(priority, this);\n\n      // If a race occurred where we cancelled the fetcher in cancel() and then called loadData here\n      // immediately after, make sure that we cancel the newly started fetcher. We don't bother\n      // checking cancelled before loadData because it's not required for correctness and would\n      // require an unlikely race to be useful.\n      if (isCancelled) {\n        cancel();\n      }\n    }\n\n    @Override\n    public void cleanup() {\n      if (exceptions != null) {\n        throwableListPool.release(exceptions);\n      }\n      exceptions = null;\n      for (DataFetcher<Data> fetcher : fetchers) {\n        fetcher.cleanup();\n      }\n    }\n\n    @Override\n    public void cancel() {\n      isCancelled = true;\n      for (DataFetcher<Data> fetcher : fetchers) {\n        fetcher.cancel();\n      }\n    }\n\n    @NonNull\n    @Override\n    public Class<Data> getDataClass() {\n      return fetchers.get(0).getDataClass();\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return fetchers.get(0).getDataSource();\n    }\n\n    @Override\n    public void onDataReady(@Nullable Data data) {\n      if (data != null) {\n        callback.onDataReady(data);\n      } else {\n        startNextOrFail();\n      }\n    }\n\n    @Override\n    public void onLoadFailed(@NonNull Exception e) {\n      Preconditions.checkNotNull(exceptions).add(e);\n      startNextOrFail();\n    }\n\n    private void startNextOrFail() {\n      if (isCancelled) {\n        return;\n      }\n\n      if (currentIndex < fetchers.size() - 1) {\n        currentIndex++;\n        loadData(priority, callback);\n      } else {\n        Preconditions.checkNotNull(exceptions);\n        callback.onLoadFailed(new GlideException(\"Fetch failed\", new ArrayList<>(exceptions)));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/MultiModelLoaderFactory.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport androidx.core.util.Pools.Pool;\nimport com.bumptech.glide.Registry.NoModelLoaderAvailableException;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * Capable of building an {@link ModelLoader} that wraps one or more other {@link ModelLoader}s for\n * a given model and data class.\n */\n// Hides Model throughout.\n@SuppressWarnings(\"TypeParameterHidesVisibleType\")\npublic class MultiModelLoaderFactory {\n  private static final Factory DEFAULT_FACTORY = new Factory();\n  private static final ModelLoader<Object, Object> EMPTY_MODEL_LOADER = new EmptyModelLoader();\n  private final List<Entry<?, ?>> entries = new ArrayList<>();\n  private final Factory factory;\n  private final Set<Entry<?, ?>> alreadyUsedEntries = new HashSet<>();\n  private final Pool<List<Throwable>> throwableListPool;\n\n  public MultiModelLoaderFactory(@NonNull Pool<List<Throwable>> throwableListPool) {\n    this(throwableListPool, DEFAULT_FACTORY);\n  }\n\n  @VisibleForTesting\n  MultiModelLoaderFactory(\n      @NonNull Pool<List<Throwable>> throwableListPool, @NonNull Factory factory) {\n    this.throwableListPool = throwableListPool;\n    this.factory = factory;\n  }\n\n  synchronized <Model, Data> void append(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {\n    add(modelClass, dataClass, factory, /* append= */ true);\n  }\n\n  synchronized <Model, Data> void prepend(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {\n    add(modelClass, dataClass, factory, /* append= */ false);\n  }\n\n  private <Model, Data> void add(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory,\n      boolean append) {\n    Entry<Model, Data> entry = new Entry<>(modelClass, dataClass, factory);\n    entries.add(append ? entries.size() : 0, entry);\n  }\n\n  @NonNull\n  synchronized <Model, Data> List<ModelLoaderFactory<? extends Model, ? extends Data>> replace(\n      @NonNull Class<Model> modelClass,\n      @NonNull Class<Data> dataClass,\n      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {\n    List<ModelLoaderFactory<? extends Model, ? extends Data>> removed =\n        remove(modelClass, dataClass);\n    append(modelClass, dataClass, factory);\n    return removed;\n  }\n\n  @NonNull\n  synchronized <Model, Data> List<ModelLoaderFactory<? extends Model, ? extends Data>> remove(\n      @NonNull Class<Model> modelClass, @NonNull Class<Data> dataClass) {\n    List<ModelLoaderFactory<? extends Model, ? extends Data>> factories = new ArrayList<>();\n    for (Iterator<Entry<?, ?>> iterator = entries.iterator(); iterator.hasNext(); ) {\n      Entry<?, ?> entry = iterator.next();\n      if (entry.handles(modelClass, dataClass)) {\n        iterator.remove();\n        factories.add(this.<Model, Data>getFactory(entry));\n      }\n    }\n    return factories;\n  }\n\n  @NonNull\n  synchronized <Model> List<ModelLoader<Model, ?>> build(@NonNull Class<Model> modelClass) {\n    try {\n      List<ModelLoader<Model, ?>> loaders = new ArrayList<>();\n      for (Entry<?, ?> entry : entries) {\n        // Avoid stack overflow recursively creating model loaders by only creating loaders in\n        // recursive requests if they haven't been created earlier in the chain. For example:\n        // A Uri loader may translate to another model, which in turn may translate back to a Uri.\n        // The original Uri loader won't be provided to the intermediate model loader, although\n        // other Uri loaders will be.\n        if (alreadyUsedEntries.contains(entry)) {\n          continue;\n        }\n        if (entry.handles(modelClass)) {\n          alreadyUsedEntries.add(entry);\n          loaders.add(this.<Model, Object>build(entry));\n          alreadyUsedEntries.remove(entry);\n        }\n      }\n      return loaders;\n    } catch (Throwable t) {\n      alreadyUsedEntries.clear();\n      throw t;\n    }\n  }\n\n  @NonNull\n  synchronized List<Class<?>> getDataClasses(@NonNull Class<?> modelClass) {\n    List<Class<?>> result = new ArrayList<>();\n    for (Entry<?, ?> entry : entries) {\n      if (!result.contains(entry.dataClass) && entry.handles(modelClass)) {\n        result.add(entry.dataClass);\n      }\n    }\n    return result;\n  }\n\n  @NonNull\n  public synchronized <Model, Data> ModelLoader<Model, Data> build(\n      @NonNull Class<Model> modelClass, @NonNull Class<Data> dataClass) {\n    try {\n      List<ModelLoader<Model, Data>> loaders = new ArrayList<>();\n      boolean ignoredAnyEntries = false;\n      for (Entry<?, ?> entry : entries) {\n        // Avoid stack overflow recursively creating model loaders by only creating loaders in\n        // recursive requests if they haven't been created earlier in the chain. For example:\n        // A Uri loader may translate to another model, which in turn may translate back to a Uri.\n        // The original Uri loader won't be provided to the intermediate model loader, although\n        // other Uri loaders will be.\n        if (alreadyUsedEntries.contains(entry)) {\n          ignoredAnyEntries = true;\n          continue;\n        }\n        if (entry.handles(modelClass, dataClass)) {\n          alreadyUsedEntries.add(entry);\n          loaders.add(this.<Model, Data>build(entry));\n          alreadyUsedEntries.remove(entry);\n        }\n      }\n      if (loaders.size() > 1) {\n        return factory.build(loaders, throwableListPool);\n      } else if (loaders.size() == 1) {\n        return loaders.get(0);\n      } else {\n        // Avoid crashing if recursion results in no loaders available. The assertion is supposed to\n        // catch completely unhandled types, recursion may mean a subtype isn't handled somewhere\n        // down the stack, which is often ok.\n        if (ignoredAnyEntries) {\n          return emptyModelLoader();\n        } else {\n          throw new NoModelLoaderAvailableException(modelClass, dataClass);\n        }\n      }\n    } catch (Throwable t) {\n      alreadyUsedEntries.clear();\n      throw t;\n    }\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  private <Model, Data> ModelLoaderFactory<Model, Data> getFactory(@NonNull Entry<?, ?> entry) {\n    return (ModelLoaderFactory<Model, Data>) entry.factory;\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  private <Model, Data> ModelLoader<Model, Data> build(@NonNull Entry<?, ?> entry) {\n    return (ModelLoader<Model, Data>) Preconditions.checkNotNull(entry.factory.build(this));\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  private static <Model, Data> ModelLoader<Model, Data> emptyModelLoader() {\n    return (ModelLoader<Model, Data>) EMPTY_MODEL_LOADER;\n  }\n\n  private static class Entry<Model, Data> {\n    private final Class<Model> modelClass;\n    @Synthetic final Class<Data> dataClass;\n    @Synthetic final ModelLoaderFactory<? extends Model, ? extends Data> factory;\n\n    public Entry(\n        @NonNull Class<Model> modelClass,\n        @NonNull Class<Data> dataClass,\n        @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {\n      this.modelClass = modelClass;\n      this.dataClass = dataClass;\n      this.factory = factory;\n    }\n\n    public boolean handles(@NonNull Class<?> modelClass, @NonNull Class<?> dataClass) {\n      return handles(modelClass) && this.dataClass.isAssignableFrom(dataClass);\n    }\n\n    public boolean handles(@NonNull Class<?> modelClass) {\n      return this.modelClass.isAssignableFrom(modelClass);\n    }\n  }\n\n  static class Factory {\n    @NonNull\n    public <Model, Data> MultiModelLoader<Model, Data> build(\n        @NonNull List<ModelLoader<Model, Data>> modelLoaders,\n        @NonNull Pool<List<Throwable>> throwableListPool) {\n      return new MultiModelLoader<>(modelLoaders, throwableListPool);\n    }\n  }\n\n  private static class EmptyModelLoader implements ModelLoader<Object, Object> {\n    @Synthetic\n    EmptyModelLoader() {}\n\n    @Nullable\n    @Override\n    public LoadData<Object> buildLoadData(\n        @NonNull Object o, int width, int height, @NonNull Options options) {\n      return null;\n    }\n\n    @Override\n    public boolean handles(@NonNull Object o) {\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ResourceLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.content.ContentResolver;\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.Resources;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport java.io.InputStream;\n\n/**\n * A model loader for handling Android resource files. Model must be an Android resource id in the\n * package of the given context.\n *\n * <p>This class should always be less preferred than {@link DirectResourceLoader} because {@link\n * DirectResourceLoader} is more efficient for {@code Drawables} owned by this package. This class\n * only handles passing through {@link Uri}s to {@link\n * com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder} and {@link\n * com.bumptech.glide.load.resource.bitmap.ResourceBitmapDecoder}. Those classes can handle assets\n * from other applications, but are not as efficient as {@link DirectResourceLoader} for assets\n * owned by this package.\n *\n * @param <Data> The type of data that will be loaded for the given android resource.\n */\npublic class ResourceLoader<Data> implements ModelLoader<Integer, Data> {\n  private static final String TAG = \"ResourceLoader\";\n  private final ModelLoader<Uri, Data> uriLoader;\n  private final Resources resources;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public ResourceLoader(Resources resources, ModelLoader<Uri, Data> uriLoader) {\n    this.resources = resources;\n    this.uriLoader = uriLoader;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull Integer model, int width, int height, @NonNull Options options) {\n    Uri uri = getResourceUri(model);\n    return uri == null ? null : uriLoader.buildLoadData(uri, width, height, options);\n  }\n\n  @Nullable\n  private Uri getResourceUri(Integer model) {\n    try {\n      return Uri.parse(\n          ContentResolver.SCHEME_ANDROID_RESOURCE\n              + \"://\"\n              + resources.getResourcePackageName(model)\n              + '/'\n              + model);\n    } catch (Resources.NotFoundException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Received invalid resource id: \" + model, e);\n      }\n      return null;\n    }\n  }\n\n  @Override\n  public boolean handles(@NonNull Integer model) {\n    // TODO: check that this is in fact a resource id.\n    return true;\n  }\n\n  /** Factory for loading {@link InputStream}s from Android resource ids. */\n  public static class StreamFactory implements ModelLoaderFactory<Integer, InputStream> {\n\n    private final Resources resources;\n\n    public StreamFactory(Resources resources) {\n      this.resources = resources;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Integer, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new ResourceLoader<>(resources, multiFactory.build(Uri.class, InputStream.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /**\n   * Factory for loading {@link ParcelFileDescriptor}s from Android resource ids.\n   *\n   * @deprecated This class is unused by Glide. {@link AssetFileDescriptorFactory} should be\n   *     preferred because it's not possible to reliably load a simple {@link\n   *     java.io.FileDescriptor} for resources.\n   */\n  @Deprecated\n  public static class FileDescriptorFactory\n      implements ModelLoaderFactory<Integer, ParcelFileDescriptor> {\n\n    private final Resources resources;\n\n    public FileDescriptorFactory(Resources resources) {\n      this.resources = resources;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Integer, ParcelFileDescriptor> build(MultiModelLoaderFactory multiFactory) {\n      return new ResourceLoader<>(\n          resources, multiFactory.build(Uri.class, ParcelFileDescriptor.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /** Loads {@link AssetFileDescriptor}s from resource ids. */\n  public static final class AssetFileDescriptorFactory\n      implements ModelLoaderFactory<Integer, AssetFileDescriptor> {\n\n    private final Resources resources;\n\n    public AssetFileDescriptorFactory(Resources resources) {\n      this.resources = resources;\n    }\n\n    @Override\n    public ModelLoader<Integer, AssetFileDescriptor> build(MultiModelLoaderFactory multiFactory) {\n      return new ResourceLoader<>(\n          resources, multiFactory.build(Uri.class, AssetFileDescriptor.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /** Factory for loading resource {@link Uri}s from Android resource ids. */\n  public static class UriFactory implements ModelLoaderFactory<Integer, Uri> {\n\n    private final Resources resources;\n\n    public UriFactory(Resources resources) {\n      this.resources = resources;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Integer, Uri> build(MultiModelLoaderFactory multiFactory) {\n      return new ResourceLoader<>(resources, UnitModelLoader.<Uri>getInstance());\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/ResourceUriLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.annotation.SuppressLint;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport java.io.InputStream;\nimport java.util.List;\n\n/**\n * Converts Resource Uris to resource ids if the resource Uri points to a resource in this package.\n *\n * <p>This class works by parsing Uris into resource ids, then delegating the resource ID load to\n * other {@link ModelLoader}s, typically {@link DirectResourceLoader}.\n *\n * <p>This class really shouldn't need to exist. If you need to load resources, just pass in the\n * integer resource id directly using {@link com.bumptech.glide.RequestManager#load(Integer)}\n * instead. It'll be more correct in terms of caching and more efficient to load. The only reason\n * we're supporting this case is for backwards compatibility.\n *\n * <p>Because this class explicitly only handles resource Uris that are from the application's\n * package, resource uris from other packages are handled by {@link UriLoader}. {@link UriLoader} is\n * even less preferred because it can only handle certain resources from raw resources and it will\n * not apply appropriate theming, RTL or night mode attributes.\n *\n * @param <DataT> The type of data produced, e.g. {@link InputStream} or {@link\n *     AssetFileDescriptor}.\n */\npublic final class ResourceUriLoader<DataT> implements ModelLoader<Uri, DataT> {\n  /**\n   * See the javadoc on {@link android.content.res.Resources#getIdentifier(java.lang.String,\n   * java.lang.String, java.lang.String)}.\n   */\n  private static final int INVALID_RESOURCE_ID = 0;\n\n  private static final String TAG = \"ResourceUriLoader\";\n\n  private final Context context;\n  private final ModelLoader<Integer, DataT> delegate;\n\n  public static ModelLoaderFactory<Uri, InputStream> newStreamFactory(Context context) {\n    return new InputStreamFactory(context);\n  }\n\n  public static ModelLoaderFactory<Uri, AssetFileDescriptor> newAssetFileDescriptorFactory(\n      Context context) {\n    return new AssetFileDescriptorFactory(context);\n  }\n\n  ResourceUriLoader(Context context, ModelLoader<Integer, DataT> delegate) {\n    this.context = context.getApplicationContext();\n    this.delegate = delegate;\n  }\n\n  @Nullable\n  @Override\n  public LoadData<DataT> buildLoadData(\n      @NonNull Uri uri, int width, int height, @NonNull Options options) {\n    List<String> pathSegments = uri.getPathSegments();\n    // android.resource//<package_name>/<resource_id>\n    if (pathSegments.size() == 1) {\n      return parseResourceIdUri(uri, width, height, options);\n    }\n    // android.resource//<package_name>/<drawable>/<resource_name>\n    if (pathSegments.size() == 2) {\n      return parseResourceNameUri(uri, width, height, options);\n    }\n    if (Log.isLoggable(TAG, Log.WARN)) {\n      Log.w(TAG, \"Failed to parse resource uri: \" + uri);\n    }\n    return null;\n  }\n\n  @Nullable\n  private LoadData<DataT> parseResourceNameUri(\n      @NonNull Uri uri, int width, int height, @NonNull Options options) {\n    List<String> pathSegments = uri.getPathSegments();\n    String resourceType = pathSegments.get(0);\n    String resourceName = pathSegments.get(1);\n\n    // Yes it's bad, but the caller has chosen to give us a resource uri...\n    @SuppressLint(\"DiscouragedApi\")\n    int identifier =\n        context.getResources().getIdentifier(resourceName, resourceType, context.getPackageName());\n    if (identifier == INVALID_RESOURCE_ID) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Failed to find resource id for: \" + uri);\n      }\n      return null;\n    }\n\n    return delegate.buildLoadData(identifier, width, height, options);\n  }\n\n  @Nullable\n  private LoadData<DataT> parseResourceIdUri(\n      @NonNull Uri uri, int width, int height, @NonNull Options options) {\n    try {\n      int resourceId = Integer.parseInt(uri.getPathSegments().get(0));\n      if (resourceId == INVALID_RESOURCE_ID) {\n        if (Log.isLoggable(TAG, Log.WARN)) {\n          Log.w(TAG, \"Failed to parse a valid non-0 resource id from: \" + uri);\n        }\n        return null;\n      }\n      return delegate.buildLoadData(resourceId, width, height, options);\n    } catch (NumberFormatException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Failed to parse resource id from: \" + uri, e);\n      }\n    }\n    return null;\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri uri) {\n    return ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())\n        && context.getPackageName().equals(uri.getAuthority());\n  }\n\n  private static final class InputStreamFactory implements ModelLoaderFactory<Uri, InputStream> {\n\n    private final Context context;\n\n    InputStreamFactory(Context context) {\n      this.context = context;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new ResourceUriLoader<>(context, multiFactory.build(Integer.class, InputStream.class));\n    }\n\n    @Override\n    public void teardown() {}\n  }\n\n  private static final class AssetFileDescriptorFactory\n      implements ModelLoaderFactory<Uri, AssetFileDescriptor> {\n\n    private final Context context;\n\n    AssetFileDescriptorFactory(Context context) {\n      this.context = context;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, AssetFileDescriptor> build(\n        @NonNull MultiModelLoaderFactory multiFactory) {\n      return new ResourceUriLoader<>(\n          context, multiFactory.build(Integer.class, AssetFileDescriptor.class));\n    }\n\n    @Override\n    public void teardown() {}\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/StreamEncoder.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Encoder;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * An {@link com.bumptech.glide.load.Encoder} that can write an {@link java.io.InputStream} to disk.\n */\npublic class StreamEncoder implements Encoder<InputStream> {\n  private static final String TAG = \"StreamEncoder\";\n  private final ArrayPool byteArrayPool;\n\n  public StreamEncoder(ArrayPool byteArrayPool) {\n    this.byteArrayPool = byteArrayPool;\n  }\n\n  @Override\n  public boolean encode(@NonNull InputStream data, @NonNull File file, @NonNull Options options) {\n    byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);\n    boolean success = false;\n    OutputStream os = null;\n    try {\n      os = new FileOutputStream(file);\n      int read;\n      while ((read = data.read(buffer)) != -1) {\n        os.write(buffer, 0, read);\n      }\n      os.close();\n      success = true;\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Failed to encode data onto the OutputStream\", e);\n      }\n    } finally {\n      if (os != null) {\n        try {\n          os.close();\n        } catch (IOException e) {\n          // Do nothing.\n        }\n      }\n      byteArrayPool.put(buffer);\n    }\n    return success;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/StringLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport android.text.TextUtils;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport java.io.File;\nimport java.io.InputStream;\n\n/**\n * A model loader for handling certain string models. Handles paths, urls, and any uri string with a\n * scheme handled by {@link android.content.ContentResolver#openInputStream(Uri)}.\n *\n * @param <Data> The type of data that will be loaded from the given {@link java.lang.String}.\n */\npublic class StringLoader<Data> implements ModelLoader<String, Data> {\n  private final ModelLoader<Uri, Data> uriLoader;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public StringLoader(ModelLoader<Uri, Data> uriLoader) {\n    this.uriLoader = uriLoader;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull String model, int width, int height, @NonNull Options options) {\n    Uri uri = parseUri(model);\n    if (uri == null || !uriLoader.handles(uri)) {\n      return null;\n    }\n    return uriLoader.buildLoadData(uri, width, height, options);\n  }\n\n  @Override\n  public boolean handles(@NonNull String model) {\n    // Avoid parsing the Uri twice and simply return null from buildLoadData if we don't handle this\n    // particular Uri type.\n    return true;\n  }\n\n  @Nullable\n  private static Uri parseUri(String model) {\n    Uri uri;\n    if (TextUtils.isEmpty(model)) {\n      return null;\n      // See https://pmd.github.io/pmd-6.0.0/pmd_rules_java_performance.html#simplifystartswith\n    } else if (model.charAt(0) == '/') {\n      uri = toFileUri(model);\n    } else {\n      uri = Uri.parse(model);\n      String scheme = uri.getScheme();\n      if (scheme == null) {\n        uri = toFileUri(model);\n      }\n    }\n    return uri;\n  }\n\n  private static Uri toFileUri(String path) {\n    return Uri.fromFile(new File(path));\n  }\n\n  /** Factory for loading {@link InputStream}s from Strings. */\n  public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {\n\n    @NonNull\n    @Override\n    public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /** Factory for loading {@link ParcelFileDescriptor}s from Strings. */\n  public static class FileDescriptorFactory\n      implements ModelLoaderFactory<String, ParcelFileDescriptor> {\n\n    @NonNull\n    @Override\n    public ModelLoader<String, ParcelFileDescriptor> build(\n        @NonNull MultiModelLoaderFactory multiFactory) {\n      return new StringLoader<>(multiFactory.build(Uri.class, ParcelFileDescriptor.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /** Loads {@link AssetFileDescriptor}s from Strings. */\n  public static final class AssetFileDescriptorFactory\n      implements ModelLoaderFactory<String, AssetFileDescriptor> {\n\n    @Override\n    public ModelLoader<String, AssetFileDescriptor> build(\n        @NonNull MultiModelLoaderFactory multiFactory) {\n      return new StringLoader<>(multiFactory.build(Uri.class, AssetFileDescriptor.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/UnitModelLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.signature.ObjectKey;\n\n/**\n * A put of helper classes that performs no loading and instead always returns the given model as\n * the data to decode.\n *\n * @param <Model> The type of model that will also be returned as decodable data.\n */\npublic class UnitModelLoader<Model> implements ModelLoader<Model, Model> {\n  @SuppressWarnings(\"deprecation\")\n  private static final UnitModelLoader<?> INSTANCE = new UnitModelLoader<>();\n\n  @SuppressWarnings(\"unchecked\")\n  public static <T> UnitModelLoader<T> getInstance() {\n    return (UnitModelLoader<T>) INSTANCE;\n  }\n\n  /**\n   * @deprecated Use {@link #getInstance()} instead.\n   */\n  // Need constructor to document deprecation, will be removed, when constructor is privatized.\n  @SuppressWarnings({\"PMD.UnnecessaryConstructor\", \"DeprecatedIsStillUsed\"})\n  @Deprecated\n  public UnitModelLoader() {\n    // Intentionally empty.\n  }\n\n  @Override\n  public LoadData<Model> buildLoadData(\n      @NonNull Model model, int width, int height, @NonNull Options options) {\n    return new LoadData<>(new ObjectKey(model), new UnitFetcher<>(model));\n  }\n\n  @Override\n  public boolean handles(@NonNull Model model) {\n    return true;\n  }\n\n  private static class UnitFetcher<Model> implements DataFetcher<Model> {\n\n    private final Model resource;\n\n    UnitFetcher(Model resource) {\n      this.resource = resource;\n    }\n\n    @Override\n    public void loadData(\n        @NonNull Priority priority, @NonNull DataCallback<? super Model> callback) {\n      callback.onDataReady(resource);\n    }\n\n    @Override\n    public void cleanup() {\n      // Do nothing.\n    }\n\n    @Override\n    public void cancel() {\n      // Do nothing.\n    }\n\n    @NonNull\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public Class<Model> getDataClass() {\n      return (Class<Model>) resource.getClass();\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.LOCAL;\n    }\n  }\n\n  /**\n   * Factory for producing {@link com.bumptech.glide.load.model.UnitModelLoader}s.\n   *\n   * @param <Model> The type of model that will also be returned as decodable data.\n   */\n  // PMD.SingleMethodSingleton false positive: https://github.com/pmd/pmd/issues/816\n  @SuppressWarnings(\"PMD.SingleMethodSingleton\")\n  public static class Factory<Model> implements ModelLoaderFactory<Model, Model> {\n    @SuppressWarnings(\"deprecation\")\n    private static final Factory<?> FACTORY = new Factory<>();\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> Factory<T> getInstance() {\n      return (Factory<T>) FACTORY;\n    }\n\n    /**\n     * @deprecated Use {@link #getInstance()} instead.\n     */\n    // Need constructor to document deprecation, will be removed, when constructor is privatized.\n    @SuppressWarnings(\"PMD.UnnecessaryConstructor\")\n    @Deprecated\n    public Factory() {\n      // Intentionally empty.\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Model, Model> build(MultiModelLoaderFactory multiFactory) {\n      return UnitModelLoader.getInstance();\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/UriLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.content.ContentResolver;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.AssetFileDescriptorLocalUriFetcher;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.FileDescriptorLocalUriFetcher;\nimport com.bumptech.glide.load.data.StreamLocalUriFetcher;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * A ModelLoader for {@link android.net.Uri}s that handles local {@link android.net.Uri}s directly\n * and routes remote {@link android.net.Uri}s to a wrapped {@link\n * com.bumptech.glide.load.model.ModelLoader} that handles {@link\n * com.bumptech.glide.load.model.GlideUrl}s.\n *\n * @param <Data> The type of data that will be retrieved for {@link android.net.Uri}s.\n */\npublic class UriLoader<Data> implements ModelLoader<Uri, Data> {\n\n  private static final Set<String> SCHEMES =\n      Collections.unmodifiableSet(\n          new HashSet<>(\n              Arrays.asList(\n                  ContentResolver.SCHEME_FILE,\n                  ContentResolver.SCHEME_CONTENT,\n                  ContentResolver.SCHEME_ANDROID_RESOURCE)));\n\n  private final LocalUriFetcherFactory<Data> factory;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public UriLoader(LocalUriFetcherFactory<Data> factory) {\n    this.factory = factory;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull Uri model, int width, int height, @NonNull Options options) {\n    return new LoadData<>(new ObjectKey(model), factory.build(model));\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri model) {\n    return SCHEMES.contains(model.getScheme());\n  }\n\n  /**\n   * Factory for obtaining a {@link DataFetcher} for a data type for a particular {@link Uri}.\n   *\n   * @param <Data> The type of data the returned {@link DataFetcher} will obtain.\n   */\n  public interface LocalUriFetcherFactory<Data> {\n    DataFetcher<Data> build(Uri uri);\n  }\n\n  /** Loads {@link InputStream}s from {@link Uri}s. */\n  public static class StreamFactory\n      implements ModelLoaderFactory<Uri, InputStream>, LocalUriFetcherFactory<InputStream> {\n\n    private final ContentResolver contentResolver;\n    private final boolean useMediaStoreApisIfAvailable;\n\n    public StreamFactory(ContentResolver contentResolver) {\n      this(contentResolver, /* useMediaStoreApisIfAvailable */ false);\n    }\n\n    /**\n     * useMediaStoreApisIfAvailable is part of an experiment and the constructor can be removed in a\n     * future version.\n     */\n    public StreamFactory(ContentResolver contentResolver, boolean useMediaStoreApisIfAvailable) {\n      this.contentResolver = contentResolver;\n      this.useMediaStoreApisIfAvailable = useMediaStoreApisIfAvailable;\n    }\n\n    @Override\n    public DataFetcher<InputStream> build(Uri uri) {\n      return new StreamLocalUriFetcher(contentResolver, uri, useMediaStoreApisIfAvailable);\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new UriLoader<>(this);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /** Loads {@link ParcelFileDescriptor}s from {@link Uri}s. */\n  public static class FileDescriptorFactory\n      implements ModelLoaderFactory<Uri, ParcelFileDescriptor>,\n          LocalUriFetcherFactory<ParcelFileDescriptor> {\n\n    private final ContentResolver contentResolver;\n    private final boolean useMediaStoreApisIfAvailable;\n\n    public FileDescriptorFactory(ContentResolver contentResolver) {\n      this(contentResolver, /* useMediaStoreApisIfAvailable */ false);\n    }\n\n    /**\n     * useMediaStoreApisIfAvailable is part of an experiment and the constructor can be removed in a\n     * future version.\n     */\n    public FileDescriptorFactory(\n        ContentResolver contentResolver, boolean useMediaStoreApisIfAvailable) {\n      this.contentResolver = contentResolver;\n      this.useMediaStoreApisIfAvailable = useMediaStoreApisIfAvailable;\n    }\n\n    @Override\n    public DataFetcher<ParcelFileDescriptor> build(Uri uri) {\n      return new FileDescriptorLocalUriFetcher(contentResolver, uri, useMediaStoreApisIfAvailable);\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, ParcelFileDescriptor> build(MultiModelLoaderFactory multiFactory) {\n      return new UriLoader<>(this);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  /** Loads {@link AssetFileDescriptor}s from {@link Uri}s. */\n  public static final class AssetFileDescriptorFactory\n      implements ModelLoaderFactory<Uri, AssetFileDescriptor>,\n          LocalUriFetcherFactory<AssetFileDescriptor> {\n\n    private final ContentResolver contentResolver;\n    private final boolean useMediaStoreApisIfAvailable;\n\n    public AssetFileDescriptorFactory(ContentResolver contentResolver) {\n      this(contentResolver, /* useMediaStoreApisIfAvailable */ false);\n    }\n\n    /**\n     * useMediaStoreApisIfAvailable is part of an experiment and the constructor can be removed in a\n     * future version.\n     */\n    public AssetFileDescriptorFactory(\n        ContentResolver contentResolver, boolean useMediaStoreApisIfAvailable) {\n      this.contentResolver = contentResolver;\n      this.useMediaStoreApisIfAvailable = useMediaStoreApisIfAvailable;\n    }\n\n    @Override\n    public ModelLoader<Uri, AssetFileDescriptor> build(MultiModelLoaderFactory multiFactory) {\n      return new UriLoader<>(this);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n\n    @Override\n    public DataFetcher<AssetFileDescriptor> build(Uri uri) {\n      return new AssetFileDescriptorLocalUriFetcher(\n          contentResolver, uri, useMediaStoreApisIfAvailable);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/UrlUriLoader.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport android.net.Uri;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Handles http/https Uris by delegating to the {@link ModelLoader} for {@link\n * com.bumptech.glide.load.model.GlideUrl GlideUrls}.\n *\n * @param <Data> The type of data this Loader will obtain for a {@link Uri}.\n */\npublic class UrlUriLoader<Data> implements ModelLoader<Uri, Data> {\n  private static final Set<String> SCHEMES =\n      Collections.unmodifiableSet(new HashSet<>(Arrays.asList(\"http\", \"https\")));\n  private final ModelLoader<GlideUrl, Data> urlLoader;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public UrlUriLoader(ModelLoader<GlideUrl, Data> urlLoader) {\n    this.urlLoader = urlLoader;\n  }\n\n  @Override\n  public LoadData<Data> buildLoadData(\n      @NonNull Uri uri, int width, int height, @NonNull Options options) {\n    GlideUrl glideUrl = new GlideUrl(uri.toString());\n    return urlLoader.buildLoadData(glideUrl, width, height, options);\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri uri) {\n    return SCHEMES.contains(uri.getScheme());\n  }\n\n  /**\n   * Loads {@link java.io.InputStream InputStreams} from {@link android.net.Uri Uris} with http or\n   * https schemes.\n   */\n  public static class StreamFactory implements ModelLoaderFactory<Uri, InputStream> {\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new UrlUriLoader<>(multiFactory.build(GlideUrl.class, InputStream.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/stream/BaseGlideUrlLoader.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport android.text.TextUtils;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.Headers;\nimport com.bumptech.glide.load.model.ModelCache;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * A base class for loading data over http/https. Can be subclassed for use with any model that can\n * be translated in to {@link java.io.InputStream} data.\n *\n * @param <Model> The type of the model.\n */\npublic abstract class BaseGlideUrlLoader<Model> implements ModelLoader<Model, InputStream> {\n  private final ModelLoader<GlideUrl, InputStream> concreteLoader;\n  @Nullable private final ModelCache<Model, GlideUrl> modelCache;\n\n  protected BaseGlideUrlLoader(ModelLoader<GlideUrl, InputStream> concreteLoader) {\n    this(concreteLoader, null);\n  }\n\n  protected BaseGlideUrlLoader(\n      ModelLoader<GlideUrl, InputStream> concreteLoader,\n      @Nullable ModelCache<Model, GlideUrl> modelCache) {\n    this.concreteLoader = concreteLoader;\n    this.modelCache = modelCache;\n  }\n\n  @Override\n  @Nullable\n  public LoadData<InputStream> buildLoadData(\n      @NonNull Model model, int width, int height, @NonNull Options options) {\n    GlideUrl result = null;\n    if (modelCache != null) {\n      result = modelCache.get(model, width, height);\n    }\n\n    if (result == null) {\n      String stringURL = getUrl(model, width, height, options);\n      if (TextUtils.isEmpty(stringURL)) {\n        return null;\n      }\n\n      result = new GlideUrl(stringURL, getHeaders(model, width, height, options));\n\n      if (modelCache != null) {\n        modelCache.put(model, width, height, result);\n      }\n    }\n\n    // TODO: this is expensive and slow to calculate every time, we should either cache these, or\n    // try to come up with a way to avoid finding them when not necessary.\n    List<String> alternateUrls = getAlternateUrls(model, width, height, options);\n    LoadData<InputStream> concreteLoaderData =\n        concreteLoader.buildLoadData(result, width, height, options);\n    if (concreteLoaderData == null || alternateUrls.isEmpty()) {\n      return concreteLoaderData;\n    } else {\n      return new LoadData<>(\n          concreteLoaderData.sourceKey,\n          getAlternateKeys(alternateUrls),\n          concreteLoaderData.fetcher);\n    }\n  }\n\n  // Creating a limited number of objects as the sole purpose of the loop.\n  @SuppressWarnings(\"PMD.AvoidInstantiatingObjectsInLoops\")\n  private static List<Key> getAlternateKeys(Collection<String> alternateUrls) {\n    List<Key> result = new ArrayList<>(alternateUrls.size());\n    for (String alternate : alternateUrls) {\n      result.add(new GlideUrl(alternate));\n    }\n    return result;\n  }\n\n  /**\n   * Returns a valid url http:// or https:// for the given model and dimensions as a string.\n   *\n   * @param model The model.\n   * @param width The width in pixels of the view/target the image will be loaded into.\n   * @param height The height in pixels of the view/target the image will be loaded into.\n   */\n  protected abstract String getUrl(Model model, int width, int height, Options options);\n\n  /**\n   * Returns a list of alternate urls for the given model, width, and height from which equivalent\n   * data can be obtained (usually the same image with the same aspect ratio, but in a larger size)\n   * as the primary url.\n   *\n   * <p>Implementing this method allows Glide to fulfill requests for bucketed images in smaller\n   * bucket sizes using already cached data for larger bucket sizes.\n   *\n   * @param width The width in pixels of the view/target the image will be loaded into.\n   * @param height The height in pixels of the view/target the image will be loaded into.\n   */\n  protected List<String> getAlternateUrls(Model model, int width, int height, Options options) {\n    return Collections.emptyList();\n  }\n\n  /**\n   * Returns the headers for the given model and dimensions as a map of strings to sets of strings,\n   * or null if no headers should be added.\n   *\n   * @param model The model.\n   * @param width The width in pixels of the view/target the image will be loaded into.\n   * @param height The height in pixels of the view/target the image will be loaded into.\n   */\n  // Public API.\n  @SuppressWarnings({\"unused\", \"WeakerAccess\"})\n  @Nullable\n  protected Headers getHeaders(Model model, int width, int height, Options options) {\n    return Headers.DEFAULT;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/stream/HttpGlideUrlLoader.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.HttpUrlFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelCache;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport java.io.InputStream;\n\n/**\n * An {@link com.bumptech.glide.load.model.ModelLoader} for translating {@link\n * com.bumptech.glide.load.model.GlideUrl} (http/https URLS) into {@link java.io.InputStream} data.\n */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {\n  /**\n   * An integer option that is used to determine the maximum connect and read timeout durations (in\n   * milliseconds) for network connections.\n   *\n   * <p>Defaults to 2500ms.\n   */\n  public static final Option<Integer> TIMEOUT =\n      Option.memory(\"com.bumptech.glide.load.model.stream.HttpGlideUrlLoader.Timeout\", 2500);\n\n  @Nullable private final ModelCache<GlideUrl, GlideUrl> modelCache;\n\n  public HttpGlideUrlLoader() {\n    this(null);\n  }\n\n  public HttpGlideUrlLoader(@Nullable ModelCache<GlideUrl, GlideUrl> modelCache) {\n    this.modelCache = modelCache;\n  }\n\n  @Override\n  public LoadData<InputStream> buildLoadData(\n      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {\n    // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time\n    // spent parsing urls.\n    GlideUrl url = model;\n    if (modelCache != null) {\n      url = modelCache.get(model, 0, 0);\n      if (url == null) {\n        modelCache.put(model, 0, 0, model);\n        url = model;\n      }\n    }\n    int timeout = options.get(TIMEOUT);\n    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));\n  }\n\n  @Override\n  public boolean handles(@NonNull GlideUrl model) {\n    return true;\n  }\n\n  /** The default factory for {@link HttpGlideUrlLoader}s. */\n  public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {\n    private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<>(500);\n\n    @NonNull\n    @Override\n    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new HttpGlideUrlLoader(modelCache);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/stream/HttpUriLoader.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport android.net.Uri;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.UrlUriLoader;\nimport java.io.InputStream;\n\n/**\n * Loads {@link InputStream}s from http or https {@link Uri}s.\n *\n * @deprecated Use {@link UrlUriLoader} instead\n */\n@Deprecated\npublic class HttpUriLoader extends UrlUriLoader<InputStream> {\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public HttpUriLoader(ModelLoader<GlideUrl, InputStream> urlLoader) {\n    super(urlLoader);\n  }\n\n  /**\n   * Factory for loading {@link InputStream}s from http/https {@link Uri}s.\n   *\n   * @deprecated Use {@link UrlUriLoader.StreamFactory} instead\n   */\n  @Deprecated\n  public static class Factory extends StreamFactory {\n    // Defer to StreamFactory's implementation\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/stream/MediaStoreImageThumbLoader.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.mediastore.MediaStoreUtil;\nimport com.bumptech.glide.load.data.mediastore.ThumbFetcher;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.InputStream;\n\n/**\n * Loads {@link InputStream}s from media store image {@link Uri}s that point to pre-generated\n * thumbnails for those {@link Uri}s in the media store.\n */\npublic class MediaStoreImageThumbLoader implements ModelLoader<Uri, InputStream> {\n  private final Context context;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public MediaStoreImageThumbLoader(Context context) {\n    this.context = context.getApplicationContext();\n  }\n\n  @Override\n  public LoadData<InputStream> buildLoadData(\n      @NonNull Uri model, int width, int height, @NonNull Options options) {\n    if (MediaStoreUtil.isThumbnailSize(width, height)) {\n      return new LoadData<>(new ObjectKey(model), ThumbFetcher.buildImageFetcher(context, model));\n    } else {\n      return null;\n    }\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri model) {\n    return MediaStoreUtil.isMediaStoreImageUri(model);\n  }\n\n  /** Factory that loads {@link InputStream}s from media store image {@link Uri}s. */\n  public static class Factory implements ModelLoaderFactory<Uri, InputStream> {\n\n    private final Context context;\n\n    public Factory(Context context) {\n      this.context = context;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new MediaStoreImageThumbLoader(context);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/stream/MediaStoreVideoThumbLoader.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.mediastore.MediaStoreUtil;\nimport com.bumptech.glide.load.data.mediastore.ThumbFetcher;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.load.resource.bitmap.VideoDecoder;\nimport com.bumptech.glide.signature.ObjectKey;\nimport java.io.InputStream;\n\n/**\n * Loads {@link InputStream}s from media store video {@link Uri}s that point to pre-generated\n * thumbnails for those {@link Uri}s in the media store.\n *\n * <p>If {@link VideoDecoder#TARGET_FRAME} is set with a non-null value that is not equal to {@link\n * VideoDecoder#DEFAULT_FRAME}, this loader will always return {@code null}. The media store does\n * not use a defined frame to generate the thumbnail, so we cannot accurately fulfill requests for\n * specific frames.\n */\npublic class MediaStoreVideoThumbLoader implements ModelLoader<Uri, InputStream> {\n  private final Context context;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public MediaStoreVideoThumbLoader(Context context) {\n    this.context = context.getApplicationContext();\n  }\n\n  @Override\n  @Nullable\n  public LoadData<InputStream> buildLoadData(\n      @NonNull Uri model, int width, int height, @NonNull Options options) {\n    if (MediaStoreUtil.isThumbnailSize(width, height) && isRequestingDefaultFrame(options)) {\n      return new LoadData<>(new ObjectKey(model), ThumbFetcher.buildVideoFetcher(context, model));\n    } else {\n      return null;\n    }\n  }\n\n  private boolean isRequestingDefaultFrame(Options options) {\n    Long specifiedFrame = options.get(VideoDecoder.TARGET_FRAME);\n    return specifiedFrame != null && specifiedFrame == VideoDecoder.DEFAULT_FRAME;\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri model) {\n    return MediaStoreUtil.isMediaStoreVideoUri(model);\n  }\n\n  /**\n   * Loads {@link InputStream}s from media store image {@link Uri}s that point to pre-generated\n   * thumbnails for those {@link Uri}s in the media store.\n   */\n  public static class Factory implements ModelLoaderFactory<Uri, InputStream> {\n\n    private final Context context;\n\n    public Factory(Context context) {\n      this.context = context;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new MediaStoreVideoThumbLoader(context);\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/stream/QMediaStoreUriLoader.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.ParcelFileDescriptor;\nimport android.provider.MediaStore;\nimport android.text.TextUtils;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.mediastore.MediaStoreUtil;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.bumptech.glide.util.Synthetic;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.InputStream;\n\n/**\n * Best effort attempt to work around various Q storage states and bugs.\n *\n * <p>In particular, HEIC images on Q cannot be decoded if they've gone through Android's exif\n * redaction, due to a bug in the implementation that corrupts the file. To avoid the issue, we need\n * to get at the un-redacted File. There are two ways we can do so:\n *\n * <ul>\n *   <li>MediaStore.setRequireOriginal\n *   <li>Querying for and opening the file via the underlying file path, rather than via {@code\n *       ContentResolver}\n * </ul>\n *\n * <p>MediaStore.setRequireOriginal will only work for applications that target Q and request and\n * currently have {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}. It's the simplest\n * change to make, but it covers the fewest applications.\n *\n * <p>Querying for the file path and opening the file directly works for applications that do not\n * target Q and for applications that do target Q but that opt in to legacy storage mode. Other\n * options are theoretically available for applications that do not target Q, but due to other bugs,\n * the only consistent way to get unredacted files is via the file system.\n *\n * <p>This class does not fix applications that target Q, do not opt in to legacy storage and that\n * don't have {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}.\n *\n * <p>Avoid using this class directly, it may be removed in any future version of Glide.\n *\n * @param <DataT> The type of data this loader will load ({@link InputStream}, {@link\n *     ParcelFileDescriptor}).\n */\n@RequiresApi(Build.VERSION_CODES.Q)\npublic final class QMediaStoreUriLoader<DataT> implements ModelLoader<Uri, DataT> {\n  private final Context context;\n  private final ModelLoader<File, DataT> fileDelegate;\n  private final ModelLoader<Uri, DataT> uriDelegate;\n  private final Class<DataT> dataClass;\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  QMediaStoreUriLoader(\n      Context context,\n      ModelLoader<File, DataT> fileDelegate,\n      ModelLoader<Uri, DataT> uriDelegate,\n      Class<DataT> dataClass) {\n    this.context = context.getApplicationContext();\n    this.fileDelegate = fileDelegate;\n    this.uriDelegate = uriDelegate;\n    this.dataClass = dataClass;\n  }\n\n  @Override\n  public LoadData<DataT> buildLoadData(\n      @NonNull Uri uri, int width, int height, @NonNull Options options) {\n    return new LoadData<>(\n        new ObjectKey(uri),\n        new QMediaStoreUriFetcher<>(\n            context, fileDelegate, uriDelegate, uri, width, height, options, dataClass));\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri uri) {\n    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && MediaStoreUtil.isMediaStoreUri(uri);\n  }\n\n  private static final class QMediaStoreUriFetcher<DataT> implements DataFetcher<DataT> {\n    private static final String[] PROJECTION = new String[] {MediaStore.MediaColumns.DATA};\n\n    private final Context context;\n    private final ModelLoader<File, DataT> fileDelegate;\n    private final ModelLoader<Uri, DataT> uriDelegate;\n    private final Uri uri;\n    private final int width;\n    private final int height;\n    private final Options options;\n    private final Class<DataT> dataClass;\n\n    private volatile boolean isCancelled;\n    @Nullable private volatile DataFetcher<DataT> delegate;\n\n    QMediaStoreUriFetcher(\n        Context context,\n        ModelLoader<File, DataT> fileDelegate,\n        ModelLoader<Uri, DataT> uriDelegate,\n        Uri uri,\n        int width,\n        int height,\n        Options options,\n        Class<DataT> dataClass) {\n      this.context = context.getApplicationContext();\n      this.fileDelegate = fileDelegate;\n      this.uriDelegate = uriDelegate;\n      this.uri = uri;\n      this.width = width;\n      this.height = height;\n      this.options = options;\n      this.dataClass = dataClass;\n    }\n\n    @Override\n    public void loadData(\n        @NonNull Priority priority, @NonNull DataCallback<? super DataT> callback) {\n      try {\n        DataFetcher<DataT> local = buildDelegateFetcher();\n        if (local == null) {\n          callback.onLoadFailed(\n              new IllegalArgumentException(\"Failed to build fetcher for: \" + uri));\n          return;\n        }\n        delegate = local;\n        if (isCancelled) {\n          cancel();\n        } else {\n          local.loadData(priority, callback);\n        }\n      } catch (FileNotFoundException e) {\n        callback.onLoadFailed(e);\n      }\n    }\n\n    @Nullable\n    private DataFetcher<DataT> buildDelegateFetcher() throws FileNotFoundException {\n      LoadData<DataT> result = buildDelegateData();\n      return result != null ? result.fetcher : null;\n    }\n\n    @Nullable\n    private LoadData<DataT> buildDelegateData() throws FileNotFoundException {\n      if (Environment.isExternalStorageLegacy()) {\n        return fileDelegate.buildLoadData(queryForFilePath(uri), width, height, options);\n      } else {\n        // Android Picker uris have MediaStore authority and does not accept requireOriginal.\n        if (MediaStoreUtil.isAndroidPickerUri(uri)) {\n          return uriDelegate.buildLoadData(uri, width, height, options);\n        }\n\n        Uri toLoad = isAccessMediaLocationGranted() ? MediaStore.setRequireOriginal(uri) : uri;\n        return uriDelegate.buildLoadData(toLoad, width, height, options);\n      }\n    }\n\n    @Override\n    public void cleanup() {\n      DataFetcher<DataT> local = delegate;\n      if (local != null) {\n        local.cleanup();\n      }\n    }\n\n    @Override\n    public void cancel() {\n      isCancelled = true;\n      DataFetcher<DataT> local = delegate;\n      if (local != null) {\n        local.cancel();\n      }\n    }\n\n    @NonNull\n    @Override\n    public Class<DataT> getDataClass() {\n      return dataClass;\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.LOCAL;\n    }\n\n    @NonNull\n    private File queryForFilePath(Uri uri) throws FileNotFoundException {\n      Cursor cursor = null;\n      try {\n        cursor =\n            context\n                .getContentResolver()\n                .query(\n                    uri,\n                    PROJECTION,\n                    /* selection= */ null,\n                    /* selectionArgs= */ null,\n                    /* sortOrder= */ null);\n        if (cursor == null || !cursor.moveToFirst()) {\n          throw new FileNotFoundException(\"Failed to media store entry for: \" + uri);\n        }\n        String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA));\n        if (TextUtils.isEmpty(path)) {\n          throw new FileNotFoundException(\"File path was empty in media store for: \" + uri);\n        }\n        return new File(path);\n      } finally {\n        if (cursor != null) {\n          cursor.close();\n        }\n      }\n    }\n\n    private boolean isAccessMediaLocationGranted() {\n      return context.checkSelfPermission(android.Manifest.permission.ACCESS_MEDIA_LOCATION)\n          == PackageManager.PERMISSION_GRANTED;\n    }\n  }\n\n  /** Factory for {@link InputStream}. */\n  @RequiresApi(Build.VERSION_CODES.Q)\n  public static final class InputStreamFactory extends Factory<InputStream> {\n    public InputStreamFactory(Context context) {\n      super(context, InputStream.class);\n    }\n  }\n\n  /** Factory for {@link ParcelFileDescriptor}. */\n  @RequiresApi(Build.VERSION_CODES.Q)\n  public static final class FileDescriptorFactory extends Factory<ParcelFileDescriptor> {\n    public FileDescriptorFactory(Context context) {\n      super(context, ParcelFileDescriptor.class);\n    }\n  }\n\n  private abstract static class Factory<DataT> implements ModelLoaderFactory<Uri, DataT> {\n\n    private final Context context;\n    private final Class<DataT> dataClass;\n\n    Factory(Context context, Class<DataT> dataClass) {\n      this.context = context;\n      this.dataClass = dataClass;\n    }\n\n    @NonNull\n    @Override\n    public final ModelLoader<Uri, DataT> build(@NonNull MultiModelLoaderFactory multiFactory) {\n      return new QMediaStoreUriLoader<>(\n          context,\n          multiFactory.build(File.class, dataClass),\n          multiFactory.build(Uri.class, dataClass),\n          dataClass);\n    }\n\n    @Override\n    public final void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/model/stream/UrlLoader.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport java.io.InputStream;\nimport java.net.URL;\n\n/**\n * A wrapper class that translates {@link java.net.URL} objects into {@link\n * com.bumptech.glide.load.model.GlideUrl} objects and then uses the wrapped {@link\n * com.bumptech.glide.load.model.ModelLoader} for {@link com.bumptech.glide.load.model.GlideUrl}s to\n * load the data.\n */\npublic class UrlLoader implements ModelLoader<URL, InputStream> {\n  private final ModelLoader<GlideUrl, InputStream> glideUrlLoader;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public UrlLoader(ModelLoader<GlideUrl, InputStream> glideUrlLoader) {\n    this.glideUrlLoader = glideUrlLoader;\n  }\n\n  @Override\n  public LoadData<InputStream> buildLoadData(\n      @NonNull URL model, int width, int height, @NonNull Options options) {\n    return glideUrlLoader.buildLoadData(new GlideUrl(model), width, height, options);\n  }\n\n  @Override\n  public boolean handles(@NonNull URL model) {\n    return true;\n  }\n\n  /** Factory for loading {@link InputStream}s from {@link URL}s. */\n  public static class StreamFactory implements ModelLoaderFactory<URL, InputStream> {\n\n    @NonNull\n    @Override\n    public ModelLoader<URL, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new UrlLoader(multiFactory.build(GlideUrl.class, InputStream.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/DefaultOnHeaderDecodedListener.java",
    "content": "package com.bumptech.glide.load.resource;\n\nimport android.graphics.ColorSpace;\nimport android.graphics.ImageDecoder;\nimport android.graphics.ImageDecoder.DecodeException;\nimport android.graphics.ImageDecoder.ImageInfo;\nimport android.graphics.ImageDecoder.OnHeaderDecodedListener;\nimport android.graphics.ImageDecoder.OnPartialImageListener;\nimport android.graphics.ImageDecoder.Source;\nimport android.os.Build;\nimport android.util.Log;\nimport android.util.Size;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.PreferredColorSpace;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.load.resource.bitmap.Downsampler;\nimport com.bumptech.glide.load.resource.bitmap.HardwareConfigState;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.Synthetic;\n\n/**\n * Downsamples, decodes, and rotates images according to their exif orientation using {@link\n * ImageDecoder}.\n *\n * <p>Obeys all options in {@link Downsampler} except for {@link\n * Downsampler#FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS}.\n */\n@RequiresApi(api = 28)\npublic final class DefaultOnHeaderDecodedListener implements OnHeaderDecodedListener {\n  private static final String TAG = \"ImageDecoder\";\n\n  @Synthetic\n  private final HardwareConfigState hardwareConfigState = HardwareConfigState.getInstance();\n\n  private final int requestedWidth;\n  private final int requestedHeight;\n  private final DecodeFormat decodeFormat;\n  private final DownsampleStrategy strategy;\n  private final boolean isHardwareConfigAllowed;\n  private final PreferredColorSpace preferredColorSpace;\n\n  public DefaultOnHeaderDecodedListener(\n      int requestedWidth, int requestedHeight, @NonNull Options options) {\n    this.requestedWidth = requestedWidth;\n    this.requestedHeight = requestedHeight;\n    decodeFormat = options.get(Downsampler.DECODE_FORMAT);\n    strategy = options.get(DownsampleStrategy.OPTION);\n    isHardwareConfigAllowed =\n        options.get(Downsampler.ALLOW_HARDWARE_CONFIG) != null\n            && options.get(Downsampler.ALLOW_HARDWARE_CONFIG);\n    preferredColorSpace = options.get(Downsampler.PREFERRED_COLOR_SPACE);\n  }\n\n  @Override\n  public void onHeaderDecoded(\n      @NonNull ImageDecoder decoder, @NonNull ImageInfo info, @NonNull Source source) {\n    if (hardwareConfigState.isHardwareConfigAllowed(\n        requestedWidth,\n        requestedHeight,\n        isHardwareConfigAllowed,\n        /* isExifOrientationRequired= */ false)) {\n      decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE);\n    } else {\n      decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);\n    }\n\n    if (decodeFormat == DecodeFormat.PREFER_RGB_565) {\n      decoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);\n    }\n\n    decoder.setOnPartialImageListener(\n        new OnPartialImageListener() {\n          @Override\n          public boolean onPartialImage(@NonNull DecodeException e) {\n            // Never return partial images.\n            return false;\n          }\n        });\n\n    Size size = info.getSize();\n    int targetWidth = requestedWidth;\n    if (requestedWidth == Target.SIZE_ORIGINAL) {\n      targetWidth = size.getWidth();\n    }\n    int targetHeight = requestedHeight;\n    if (requestedHeight == Target.SIZE_ORIGINAL) {\n      targetHeight = size.getHeight();\n    }\n\n    float scaleFactor =\n        strategy.getScaleFactor(size.getWidth(), size.getHeight(), targetWidth, targetHeight);\n\n    int resizeWidth = Math.round(scaleFactor * size.getWidth());\n    int resizeHeight = Math.round(scaleFactor * size.getHeight());\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(\n          TAG,\n          \"Resizing\"\n              + \" from [\"\n              + size.getWidth()\n              + \"x\"\n              + size.getHeight()\n              + \"]\"\n              + \" to [\"\n              + resizeWidth\n              + \"x\"\n              + resizeHeight\n              + \"]\"\n              + \" scaleFactor: \"\n              + scaleFactor);\n    }\n\n    decoder.setTargetSize(resizeWidth, resizeHeight);\n    if (preferredColorSpace != null) {\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n        boolean isP3Eligible =\n            preferredColorSpace == PreferredColorSpace.DISPLAY_P3\n                && info.getColorSpace() != null\n                && info.getColorSpace().isWideGamut();\n        decoder.setTargetColorSpace(\n            ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB));\n      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n        decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/SimpleResource.java",
    "content": "package com.bumptech.glide.load.resource;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * Simple wrapper for an arbitrary object which helps to satisfy some of the glide engine's\n * contracts. <b>Suggested usages only include resource object which don't have size and cannot be\n * recycled/closed.</b>\n *\n * @param <T> type of the wrapped resource\n */\n// TODO: there isn't much point in caching these...\npublic class SimpleResource<T> implements Resource<T> {\n  protected final T data;\n\n  public SimpleResource(@NonNull T data) {\n    this.data = Preconditions.checkNotNull(data);\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public Class<T> getResourceClass() {\n    return (Class<T>) data.getClass();\n  }\n\n  @NonNull\n  @Override\n  public final T get() {\n    return data;\n  }\n\n  @Override\n  public final int getSize() {\n    return 1;\n  }\n\n  @Override\n  public void recycle() {\n    // no op\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/UnitTransformation.java",
    "content": "package com.bumptech.glide.load.resource;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.security.MessageDigest;\n\n/**\n * A no-op Transformation that simply returns the given resource.\n *\n * @param <T> The type of the resource that will always be returned unmodified.\n */\npublic final class UnitTransformation<T> implements Transformation<T> {\n  private static final Transformation<?> TRANSFORMATION = new UnitTransformation<>();\n\n  /**\n   * Returns a UnitTransformation for the given type.\n   *\n   * @param <T> The type of the resource to be transformed.\n   */\n  @SuppressWarnings(\"unchecked\")\n  @NonNull\n  public static <T> UnitTransformation<T> get() {\n    return (UnitTransformation<T>) TRANSFORMATION;\n  }\n\n  private UnitTransformation() {\n    // Only accessible as a singleton.\n  }\n\n  @NonNull\n  @Override\n  public Resource<T> transform(\n      @NonNull Context context, @NonNull Resource<T> resource, int outWidth, int outHeight) {\n    return resource;\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    // Do nothing.\n  }\n\n  /* Use default implementations of equals and hashcode. */\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\n\n/**\n * Decodes an {@link android.graphics.drawable.BitmapDrawable} for a data type.\n *\n * @param <DataType> The type of data that will be decoded.\n */\npublic class BitmapDrawableDecoder<DataType> implements ResourceDecoder<DataType, BitmapDrawable> {\n\n  private final ResourceDecoder<DataType, Bitmap> decoder;\n  private final Resources resources;\n\n  // Public API.\n  @SuppressWarnings({\"unused\", \"WeakerAccess\"})\n  public BitmapDrawableDecoder(Context context, ResourceDecoder<DataType, Bitmap> decoder) {\n    this(context.getResources(), decoder);\n  }\n\n  /**\n   * @deprecated Use {@link #BitmapDrawableDecoder(Context, ResourceDecoder)}, {@code bitmapPool} is\n   *     ignored.\n   */\n  @Deprecated\n  public BitmapDrawableDecoder(\n      Resources resources,\n      @SuppressWarnings(\"unused\") BitmapPool bitmapPool,\n      ResourceDecoder<DataType, Bitmap> decoder) {\n    this(resources, decoder);\n  }\n\n  public BitmapDrawableDecoder(\n      @NonNull Resources resources, @NonNull ResourceDecoder<DataType, Bitmap> decoder) {\n    this.resources = Preconditions.checkNotNull(resources);\n    this.decoder = Preconditions.checkNotNull(decoder);\n  }\n\n  @Override\n  public boolean handles(@NonNull DataType source, @NonNull Options options) throws IOException {\n    return decoder.handles(source, options);\n  }\n\n  @Override\n  public Resource<BitmapDrawable> decode(\n      @NonNull DataType source, int width, int height, @NonNull Options options)\n      throws IOException {\n    Resource<Bitmap> bitmapResource = decoder.decode(source, width, height, options);\n    return LazyBitmapDrawableResource.obtain(resources, bitmapResource);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableEncoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.EncodeStrategy;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport java.io.File;\n\n/** Encodes {@link android.graphics.drawable.BitmapDrawable}s. */\npublic class BitmapDrawableEncoder implements ResourceEncoder<BitmapDrawable> {\n\n  private final BitmapPool bitmapPool;\n  private final ResourceEncoder<Bitmap> encoder;\n\n  public BitmapDrawableEncoder(BitmapPool bitmapPool, ResourceEncoder<Bitmap> encoder) {\n    this.bitmapPool = bitmapPool;\n    this.encoder = encoder;\n  }\n\n  @Override\n  public boolean encode(\n      @NonNull Resource<BitmapDrawable> data, @NonNull File file, @NonNull Options options) {\n    return encoder.encode(new BitmapResource(data.get().getBitmap(), bitmapPool), file, options);\n  }\n\n  @NonNull\n  @Override\n  public EncodeStrategy getEncodeStrategy(@NonNull Options options) {\n    return encoder.getEncodeStrategy(options);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableResource.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.Initializable;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.drawable.DrawableResource;\nimport com.bumptech.glide.util.Util;\n\n/**\n * A {@link com.bumptech.glide.load.engine.Resource} that wraps an {@link\n * android.graphics.drawable.BitmapDrawable}\n *\n * <p>This class ensures that every call to {@link #get()}} always returns a new {@link\n * android.graphics.drawable.BitmapDrawable} to avoid rendering issues if used in multiple views and\n * is also responsible for returning the underlying {@link android.graphics.Bitmap} to the given\n * {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} when the resource is recycled.\n */\npublic class BitmapDrawableResource extends DrawableResource<BitmapDrawable>\n    implements Initializable {\n  private final BitmapPool bitmapPool;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public BitmapDrawableResource(BitmapDrawable drawable, BitmapPool bitmapPool) {\n    super(drawable);\n    this.bitmapPool = bitmapPool;\n  }\n\n  @NonNull\n  @Override\n  public Class<BitmapDrawable> getResourceClass() {\n    return BitmapDrawable.class;\n  }\n\n  @Override\n  public int getSize() {\n    return Util.getBitmapByteSize(drawable.getBitmap());\n  }\n\n  @Override\n  public void recycle() {\n    bitmapPool.put(drawable.getBitmap());\n  }\n\n  @Override\n  public void initialize() {\n    drawable.getBitmap().prepareToDraw();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableTransformation.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.Preconditions;\nimport java.security.MessageDigest;\n\n/**\n * Transforms {@link android.graphics.drawable.BitmapDrawable}s.\n *\n * @deprecated Use {@link DrawableTransformation} instead.\n */\n@Deprecated\npublic class BitmapDrawableTransformation implements Transformation<BitmapDrawable> {\n\n  private final Transformation<Drawable> wrapped;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public BitmapDrawableTransformation(Transformation<Bitmap> wrapped) {\n    this.wrapped =\n        Preconditions.checkNotNull(new DrawableTransformation(wrapped, /* isRequired= */ false));\n  }\n\n  @NonNull\n  @Override\n  public Resource<BitmapDrawable> transform(\n      @NonNull Context context,\n      @NonNull Resource<BitmapDrawable> drawableResourceToTransform,\n      int outWidth,\n      int outHeight) {\n    Resource<Drawable> toTransform = convertToDrawableResource(drawableResourceToTransform);\n    Resource<Drawable> transformed = wrapped.transform(context, toTransform, outWidth, outHeight);\n    return convertToBitmapDrawableResource(transformed);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static Resource<BitmapDrawable> convertToBitmapDrawableResource(\n      Resource<Drawable> resource) {\n    if (!(resource.get() instanceof BitmapDrawable)) {\n      throw new IllegalArgumentException(\n          \"Wrapped transformation unexpectedly returned a non BitmapDrawable resource: \"\n              + resource.get());\n    }\n    return (Resource<BitmapDrawable>) (Resource<?>) resource;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static Resource<Drawable> convertToDrawableResource(Resource<BitmapDrawable> toConvert) {\n    return (Resource<Drawable>) (Resource<? extends Drawable>) toConvert;\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof BitmapDrawableTransformation) {\n      BitmapDrawableTransformation other = (BitmapDrawableTransformation) o;\n      return wrapped.equals(other.wrapped);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return wrapped.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    wrapped.updateDiskCacheKey(messageDigest);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.EncodeStrategy;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.load.data.BufferedOutputStream;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.util.LogTime;\nimport com.bumptech.glide.util.Util;\nimport com.bumptech.glide.util.pool.GlideTrace;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * An {@link com.bumptech.glide.load.ResourceEncoder} that writes {@link android.graphics.Bitmap}s\n * to {@link java.io.OutputStream}s.\n *\n * <p>{@link android.graphics.Bitmap}s that return true from {@link android.graphics.Bitmap#hasAlpha\n * ()}} are written using {@link android.graphics.Bitmap.CompressFormat#PNG} to preserve alpha and\n * all other bitmaps are written using {@link android.graphics.Bitmap.CompressFormat#JPEG}.\n *\n * @see android.graphics.Bitmap#compress(android.graphics.Bitmap.CompressFormat, int,\n *     java.io.OutputStream)\n */\npublic class BitmapEncoder implements ResourceEncoder<Bitmap> {\n  /**\n   * An integer option between 0 and 100 that is used as the compression quality.\n   *\n   * <p>Defaults to 90.\n   */\n  public static final Option<Integer> COMPRESSION_QUALITY =\n      Option.memory(\"com.bumptech.glide.load.resource.bitmap.BitmapEncoder.CompressionQuality\", 90);\n\n  /**\n   * An {@link android.graphics.Bitmap.CompressFormat} option used as the format to encode the\n   * {@link android.graphics.Bitmap}.\n   *\n   * <p>Defaults to {@link android.graphics.Bitmap.CompressFormat#JPEG} for images without alpha and\n   * {@link android.graphics.Bitmap.CompressFormat#PNG} for images with alpha.\n   */\n  public static final Option<Bitmap.CompressFormat> COMPRESSION_FORMAT =\n      Option.memory(\"com.bumptech.glide.load.resource.bitmap.BitmapEncoder.CompressionFormat\");\n\n  private static final String TAG = \"BitmapEncoder\";\n  @Nullable private final ArrayPool arrayPool;\n\n  public BitmapEncoder(@NonNull ArrayPool arrayPool) {\n    this.arrayPool = arrayPool;\n  }\n\n  /**\n   * @deprecated Use {@link #BitmapEncoder(ArrayPool)} instead.\n   */\n  @Deprecated\n  public BitmapEncoder() {\n    arrayPool = null;\n  }\n\n  @Override\n  public boolean encode(\n      @NonNull Resource<Bitmap> resource, @NonNull File file, @NonNull Options options) {\n    final Bitmap bitmap = resource.get();\n    Bitmap.CompressFormat format = getFormat(bitmap, options);\n    GlideTrace.beginSectionFormat(\n        \"encode: [%dx%d] %s\", bitmap.getWidth(), bitmap.getHeight(), format);\n    try {\n      long start = LogTime.getLogTime();\n      int quality = options.get(COMPRESSION_QUALITY);\n\n      boolean success = false;\n      OutputStream os = null;\n      try {\n        os = new FileOutputStream(file);\n        if (arrayPool != null) {\n          os = new BufferedOutputStream(os, arrayPool);\n        }\n        bitmap.compress(format, quality, os);\n        os.close();\n        success = true;\n      } catch (IOException e) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Failed to encode Bitmap\", e);\n        }\n      } finally {\n        if (os != null) {\n          try {\n            os.close();\n          } catch (IOException e) {\n            // Do nothing.\n          }\n        }\n      }\n\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(\n            TAG,\n            \"Compressed with type: \"\n                + format\n                + \" of size \"\n                + Util.getBitmapByteSize(bitmap)\n                + \" in \"\n                + LogTime.getElapsedMillis(start)\n                + \", options format: \"\n                + options.get(COMPRESSION_FORMAT)\n                + \", hasAlpha: \"\n                + bitmap.hasAlpha());\n      }\n      return success;\n    } finally {\n      GlideTrace.endSection();\n    }\n  }\n\n  private Bitmap.CompressFormat getFormat(Bitmap bitmap, Options options) {\n    Bitmap.CompressFormat format = options.get(COMPRESSION_FORMAT);\n    if (format != null) {\n      return format;\n    } else if (bitmap.hasAlpha()) {\n      return Bitmap.CompressFormat.PNG;\n    } else {\n      return Bitmap.CompressFormat.JPEG;\n    }\n  }\n\n  @NonNull\n  @Override\n  public EncodeStrategy getEncodeStrategy(@NonNull Options options) {\n    return EncodeStrategy.TRANSFORMED;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapImageDecoderResourceDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.ImageDecoder;\nimport android.graphics.ImageDecoder.Source;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.load.resource.DefaultOnHeaderDecodedListener;\nimport java.io.IOException;\n\n/** {@link Bitmap} specific implementation of {@link DefaultOnHeaderDecodedListener}. */\n@RequiresApi(api = 28)\npublic final class BitmapImageDecoderResourceDecoder implements ResourceDecoder<Source, Bitmap> {\n  private static final String TAG = \"BitmapImageDecoder\";\n  private final BitmapPool bitmapPool = new BitmapPoolAdapter();\n\n  @Override\n  public boolean handles(@NonNull Source source, @NonNull Options options) throws IOException {\n    return true;\n  }\n\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull Source source, int width, int height, @NonNull Options options) throws IOException {\n    Bitmap result =\n        ImageDecoder.decodeBitmap(\n            source, new DefaultOnHeaderDecodedListener(width, height, options));\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(\n          TAG,\n          \"Decoded\"\n              + \" [\"\n              + result.getWidth()\n              + \"x\"\n              + result.getHeight()\n              + \"]\"\n              + \" for [\"\n              + width\n              + \"x\"\n              + height\n              + \"]\");\n    }\n    return new BitmapResource(result, bitmapPool);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapResource.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.engine.Initializable;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Util;\n\n/** A resource wrapping a {@link android.graphics.Bitmap} object. */\npublic class BitmapResource implements Resource<Bitmap>, Initializable {\n  private final Bitmap bitmap;\n  private final BitmapPool bitmapPool;\n\n  /**\n   * Returns a new {@link BitmapResource} wrapping the given {@link Bitmap} if the Bitmap is\n   * non-null or null if the given Bitmap is null.\n   *\n   * @param bitmap A Bitmap.\n   * @param bitmapPool A non-null {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}.\n   */\n  @Nullable\n  public static BitmapResource obtain(@Nullable Bitmap bitmap, @NonNull BitmapPool bitmapPool) {\n    if (bitmap == null) {\n      return null;\n    } else {\n      return new BitmapResource(bitmap, bitmapPool);\n    }\n  }\n\n  public BitmapResource(@NonNull Bitmap bitmap, @NonNull BitmapPool bitmapPool) {\n    this.bitmap = Preconditions.checkNotNull(bitmap, \"Bitmap must not be null\");\n    this.bitmapPool = Preconditions.checkNotNull(bitmapPool, \"BitmapPool must not be null\");\n  }\n\n  @NonNull\n  @Override\n  public Class<Bitmap> getResourceClass() {\n    return Bitmap.class;\n  }\n\n  @NonNull\n  @Override\n  public Bitmap get() {\n    return bitmap;\n  }\n\n  @Override\n  public int getSize() {\n    return Util.getBitmapByteSize(bitmap);\n  }\n\n  @Override\n  public void recycle() {\n    bitmapPool.put(bitmap);\n  }\n\n  @Override\n  public void initialize() {\n    bitmap.prepareToDraw();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapTransformation.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.Util;\nimport java.nio.charset.Charset;\nimport java.security.MessageDigest;\n\n/**\n * A simple {@link com.bumptech.glide.load.Transformation} for transforming {@link\n * android.graphics.Bitmap}s that abstracts away dealing with {@link\n * com.bumptech.glide.load.engine.Resource} objects for subclasses.\n *\n * <p>Use cases will look something like this:\n *\n * <pre>{@code\n * public class FillSpace extends BitmapTransformation {\n *     private static final String ID = \"com.bumptech.glide.transformations.FillSpace\";\n *     private static final byte[] ID_BYTES = ID.getBytes(Charset.forName(\"UTF-8\"));\n *\n *     {@literal @Override}\n *     public Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {\n *         if (toTransform.getWidth() == outWidth && toTransform.getHeight() == outHeight) {\n *             return toTransform;\n *         }\n *\n *         return Bitmap.createScaledBitmap(toTransform, outWidth, outHeight, true);\n *     }\n *\n *     {@literal @Override}\n *     public boolean equals(Object o) {\n *       return o instanceof FillSpace;\n *     }\n *\n *     {@literal @Override}\n *     public int hashCode() {\n *       return ID.hashCode();\n *     }\n *\n *     {@literal @Override}\n *     public void updateDiskCacheKey(MessageDigest messageDigest) {\n *       messageDigest.update(ID_BYTES);\n *     }\n * }\n * }</pre>\n *\n * <p>Using the fully qualified class name as a static final {@link String} (not {@link\n * Class#getName()} to avoid proguard obfuscation) is an easy way to implement {@link\n * #updateDiskCacheKey(java.security.MessageDigest)}} correctly. If additional arguments are\n * required they can be passed in to the constructor of the {@code Transformation} and then used to\n * update the {@link java.security.MessageDigest} passed in to {@link\n * #updateDiskCacheKey(MessageDigest)}. If arguments are primitive types, they can typically easily\n * be serialized using {@link java.nio.ByteBuffer}. {@link String} types can be serialized with\n * {@link String#getBytes(Charset)} using the constant {@link #CHARSET}.\n *\n * <p>As with all {@link Transformation}s, all subclasses <em>must</em> implement {@link\n * #equals(Object)} and {@link #hashCode()} for memory caching to work correctly.\n */\npublic abstract class BitmapTransformation implements Transformation<Bitmap> {\n\n  @NonNull\n  @Override\n  public final Resource<Bitmap> transform(\n      @NonNull Context context, @NonNull Resource<Bitmap> resource, int outWidth, int outHeight) {\n    if (!Util.isValidDimensions(outWidth, outHeight)) {\n      throw new IllegalArgumentException(\n          \"Cannot apply transformation on width: \"\n              + outWidth\n              + \" or height: \"\n              + outHeight\n              + \" less than or equal to zero and not Target.SIZE_ORIGINAL\");\n    }\n    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();\n    Bitmap toTransform = resource.get();\n    int targetWidth = outWidth == Target.SIZE_ORIGINAL ? toTransform.getWidth() : outWidth;\n    int targetHeight = outHeight == Target.SIZE_ORIGINAL ? toTransform.getHeight() : outHeight;\n    Bitmap transformed = transform(bitmapPool, toTransform, targetWidth, targetHeight);\n\n    final Resource<Bitmap> result;\n    if (toTransform.equals(transformed)) {\n      result = resource;\n    } else {\n      result = BitmapResource.obtain(transformed, bitmapPool);\n    }\n    return result;\n  }\n\n  /**\n   * Transforms the given {@link android.graphics.Bitmap} based on the given dimensions and returns\n   * the transformed result.\n   *\n   * <p>The provided Bitmap, toTransform, should not be recycled or returned to the pool. Glide will\n   * automatically recycle and/or reuse toTransform if the transformation returns a different\n   * Bitmap. Similarly implementations should never recycle or return Bitmaps that are returned as\n   * the result of this method. Recycling or returning the provided and/or the returned Bitmap to\n   * the pool will lead to a variety of runtime exceptions and drawing errors. See #408 for an\n   * example. If the implementation obtains and discards intermediate Bitmaps, they may safely be\n   * returned to the BitmapPool and/or recycled.\n   *\n   * <p>outWidth and outHeight will never be {@link\n   * com.bumptech.glide.request.target.Target#SIZE_ORIGINAL}, this class converts them to be the\n   * size of the Bitmap we're going to transform before calling this method.\n   *\n   * @param pool A {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} that can be used\n   *     to obtain and return intermediate {@link Bitmap}s used in this transformation. For every\n   *     {@link android.graphics.Bitmap} obtained from the pool during this transformation, a {@link\n   *     android.graphics.Bitmap} must also be returned.\n   * @param toTransform The {@link android.graphics.Bitmap} to transform.\n   * @param outWidth The ideal width of the transformed bitmap (the transformed width does not need\n   *     to match exactly).\n   * @param outHeight The ideal height of the transformed bitmap (the transformed height does not\n   *     need to match exactly).\n   */\n  protected abstract Bitmap transform(\n      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapTransitionOptions.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.request.transition.BitmapTransitionFactory;\nimport com.bumptech.glide.request.transition.DrawableCrossFadeFactory;\nimport com.bumptech.glide.request.transition.TransitionFactory;\n\n/** Contains {@link Bitmap} specific animation options. */\n// Public API.\n@SuppressWarnings({\"unused\", \"WeakerAccess\"})\npublic final class BitmapTransitionOptions\n    extends TransitionOptions<BitmapTransitionOptions, Bitmap> {\n\n  /**\n   * Returns a {@link BitmapTransitionOptions} object that enables a cross fade animation.\n   *\n   * @see #crossFade()\n   */\n  @NonNull\n  public static BitmapTransitionOptions withCrossFade() {\n    return new BitmapTransitionOptions().crossFade();\n  }\n\n  /**\n   * Returns a {@link BitmapTransitionOptions} object that enables a cross fade animation.\n   *\n   * @see #crossFade(int)\n   */\n  @NonNull\n  public static BitmapTransitionOptions withCrossFade(int duration) {\n    return new BitmapTransitionOptions().crossFade(duration);\n  }\n\n  /**\n   * Returns a {@link BitmapTransitionOptions} object that enables a cross fade animation.\n   *\n   * @see #crossFade(DrawableCrossFadeFactory)\n   */\n  @NonNull\n  public static BitmapTransitionOptions withCrossFade(\n      @NonNull DrawableCrossFadeFactory drawableCrossFadeFactory) {\n    return new BitmapTransitionOptions().crossFade(drawableCrossFadeFactory);\n  }\n\n  /**\n   * Returns a {@link BitmapTransitionOptions} object that enables a cross fade animation.\n   *\n   * @see #crossFade(DrawableCrossFadeFactory.Builder)\n   */\n  @NonNull\n  public static BitmapTransitionOptions withCrossFade(\n      @NonNull DrawableCrossFadeFactory.Builder builder) {\n    return new BitmapTransitionOptions().crossFade(builder);\n  }\n\n  /**\n   * Returns a {@link BitmapTransitionOptions} object that enables a any animation that is possible\n   * on drawables.\n   *\n   * @see #transitionUsing(TransitionFactory)\n   */\n  @NonNull\n  public static BitmapTransitionOptions withWrapped(\n      @NonNull TransitionFactory<Drawable> drawableCrossFadeFactory) {\n    return new BitmapTransitionOptions().transitionUsing(drawableCrossFadeFactory);\n  }\n\n  /**\n   * Returns a {@link BitmapTransitionOptions} object that uses the given transition factory.\n   *\n   * @see com.bumptech.glide.GenericTransitionOptions#with(TransitionFactory)\n   */\n  @NonNull\n  public static BitmapTransitionOptions with(@NonNull TransitionFactory<Bitmap> transitionFactory) {\n    return new BitmapTransitionOptions().transition(transitionFactory);\n  }\n\n  /**\n   * Enables a cross fade animation between both the placeholder and the first resource and between\n   * subsequent resources (if thumbnails are used).\n   */\n  @NonNull\n  public BitmapTransitionOptions crossFade() {\n    return crossFade(new DrawableCrossFadeFactory.Builder());\n  }\n\n  /**\n   * Enables a cross fade animation between both the placeholder and the first resource and between\n   * subsequent resources (if thumbnails are used).\n   *\n   * @param duration The duration of the animation, see {@code\n   *     DrawableCrossFadeFactory.Builder(int)}.\n   * @see com.bumptech.glide.request.transition.DrawableCrossFadeFactory.Builder\n   */\n  @NonNull\n  public BitmapTransitionOptions crossFade(int duration) {\n    return crossFade(new DrawableCrossFadeFactory.Builder(duration));\n  }\n\n  /**\n   * Enables a cross fade animation between both the placeholder and the first resource and between\n   * subsequent resources (if thumbnails are used).\n   */\n  @NonNull\n  public BitmapTransitionOptions crossFade(\n      @NonNull DrawableCrossFadeFactory drawableCrossFadeFactory) {\n    return transitionUsing(drawableCrossFadeFactory);\n  }\n\n  /** Enables a any Drawable based animation to run on Bitmaps as well. */\n  @NonNull\n  public BitmapTransitionOptions transitionUsing(\n      @NonNull TransitionFactory<Drawable> drawableCrossFadeFactory) {\n    return transition(new BitmapTransitionFactory(drawableCrossFadeFactory));\n  }\n\n  /**\n   * Enables a cross fade animation between both the placeholder and the first resource and between\n   * subsequent resources (if thumbnails are used).\n   */\n  @NonNull\n  public BitmapTransitionOptions crossFade(@NonNull DrawableCrossFadeFactory.Builder builder) {\n    return transitionUsing(builder.build());\n  }\n\n  // Make sure that we're not equal to any other concrete implementation of TransitionOptions.\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof BitmapTransitionOptions && super.equals(o);\n  }\n\n  // Our class doesn't include any additional properties, so we don't need to modify hashcode, but\n  // keep it here as a reminder in case we add properties.\n  @SuppressWarnings(\"PMD.UselessOverridingMethod\")\n  @Override\n  public int hashCode() {\n    return super.hashCode();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/ByteBufferBitmapDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\n\n/** Decodes {@link android.graphics.Bitmap Bitmaps} from {@link java.nio.ByteBuffer ByteBuffers}. */\npublic class ByteBufferBitmapDecoder implements ResourceDecoder<ByteBuffer, Bitmap> {\n  private final Downsampler downsampler;\n\n  public ByteBufferBitmapDecoder(Downsampler downsampler) {\n    this.downsampler = downsampler;\n  }\n\n  @Override\n  public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {\n    return downsampler.handles(source);\n  }\n\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull ByteBuffer source, int width, int height, @NonNull Options options)\n      throws IOException {\n    return downsampler.decode(source, width, height, options);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/ByteBufferBitmapImageDecoderResourceDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.ImageDecoder;\nimport android.graphics.ImageDecoder.Source;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\n\n/**\n * {@link ByteBuffer} specific implementation of {@link\n * ByteBufferBitmapImageDecoderResourceDecoder}.\n */\n@RequiresApi(api = 28)\npublic final class ByteBufferBitmapImageDecoderResourceDecoder\n    implements ResourceDecoder<ByteBuffer, Bitmap> {\n  private final BitmapImageDecoderResourceDecoder wrapped = new BitmapImageDecoderResourceDecoder();\n\n  @Override\n  public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) throws IOException {\n    return true;\n  }\n\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull ByteBuffer buffer, int width, int height, @NonNull Options options)\n      throws IOException {\n    Source source = ImageDecoder.createSource(buffer);\n    return wrapped.decode(source, width, height, options);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/CenterCrop.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport java.security.MessageDigest;\n\n/**\n * Scale the image so that either the width of the image matches the given width and the height of\n * the image is greater than the given height or vice versa, and then crop the larger dimension to\n * match the given dimension.\n *\n * <p>Does not maintain the image's aspect ratio\n */\npublic class CenterCrop extends BitmapTransformation {\n  private static final String ID = \"com.bumptech.glide.load.resource.bitmap.CenterCrop\";\n  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);\n\n  @Override\n  protected Bitmap transform(\n      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n    return TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof CenterCrop;\n  }\n\n  @Override\n  public int hashCode() {\n    return ID.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(ID_BYTES);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/CenterInside.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport java.security.MessageDigest;\n\n/**\n * Returns the image with its original size if its dimensions match or are smaller than the\n * target's, couple with {@link android.widget.ImageView.ScaleType#CENTER_INSIDE} in order to center\n * it in Target. If not, then it is scaled so that one of the dimensions of the image will be equal\n * to the given dimension and the other will be less than the given dimension (maintaining the\n * image's aspect ratio).\n */\npublic class CenterInside extends BitmapTransformation {\n  private static final String ID = \"com.bumptech.glide.load.resource.bitmap.CenterInside\";\n  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);\n\n  @Override\n  protected Bitmap transform(\n      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n    return TransformationUtils.centerInside(pool, toTransform, outWidth, outHeight);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof CenterInside;\n  }\n\n  @Override\n  public int hashCode() {\n    return ID.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(ID_BYTES);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/CircleCrop.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport java.security.MessageDigest;\n\n/**\n * A Glide {@link BitmapTransformation} to circle crop an image. Behaves similar to a {@link\n * FitCenter} transform, but the resulting image is masked to a circle.\n *\n * <p>Uses a PorterDuff blend mode, see http://ssp.impulsetrain.com/porterduff.html.\n */\npublic class CircleCrop extends BitmapTransformation {\n  // The version of this transformation, incremented to correct an error in a previous version.\n  // See #455.\n  private static final int VERSION = 1;\n  private static final String ID = \"com.bumptech.glide.load.resource.bitmap.CircleCrop.\" + VERSION;\n  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);\n\n  // Bitmap doesn't implement equals, so == and .equals are equivalent here.\n  @SuppressWarnings(\"PMD.CompareObjectsWithEquals\")\n  @Override\n  protected Bitmap transform(\n      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n    return TransformationUtils.circleCrop(pool, toTransform, outWidth, outHeight);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof CircleCrop;\n  }\n\n  @Override\n  public int hashCode() {\n    return ID.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(ID_BYTES);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/DefaultImageHeaderParser.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.load.ImageHeaderParser.ImageType.ANIMATED_AVIF;\nimport static com.bumptech.glide.load.ImageHeaderParser.ImageType.ANIMATED_WEBP;\nimport static com.bumptech.glide.load.ImageHeaderParser.ImageType.AVIF;\nimport static com.bumptech.glide.load.ImageHeaderParser.ImageType.GIF;\nimport static com.bumptech.glide.load.ImageHeaderParser.ImageType.JPEG;\nimport static com.bumptech.glide.load.ImageHeaderParser.ImageType.PNG;\nimport static com.bumptech.glide.load.ImageHeaderParser.ImageType.PNG_A;\nimport static com.bumptech.glide.load.ImageHeaderParser.ImageType.UNKNOWN;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.charset.Charset;\n\n/** A class for parsing the exif orientation and other data from an image header. */\npublic final class DefaultImageHeaderParser implements ImageHeaderParser {\n  // Due to https://code.google.com/p/android/issues/detail?id=97751.\n  // TAG needs to be under 23 chars, so \"Default\" > \"Dflt\".\n  private static final String TAG = \"DfltImageHeaderParser\";\n\n  private static final int GIF_HEADER = 0x474946;\n  private static final int PNG_HEADER = 0x89504E47;\n  static final int EXIF_MAGIC_NUMBER = 0xFFD8;\n  // \"MM\".\n  private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D;\n  // \"II\".\n  private static final int INTEL_TIFF_MAGIC_NUMBER = 0x4949;\n  private static final String JPEG_EXIF_SEGMENT_PREAMBLE = \"Exif\\0\\0\";\n  static final byte[] JPEG_EXIF_SEGMENT_PREAMBLE_BYTES =\n      JPEG_EXIF_SEGMENT_PREAMBLE.getBytes(Charset.forName(\"UTF-8\"));\n  private static final String JPEG_MPF_SEGMENT_PREAMBLE = \"MPF\";\n  static final byte[] JPEG_MPF_SEGMENT_PREAMBLE_BYTES =\n      JPEG_MPF_SEGMENT_PREAMBLE.getBytes(Charset.forName(\"UTF-8\"));\n  private static final int SEGMENT_SOS = 0xDA;\n  private static final int MARKER_EOI = 0xD9;\n  static final int SEGMENT_START_ID = 0xFF;\n  static final int EXIF_SEGMENT_TYPE = 0xE1;\n  static final int APP2_SEGMENT_TYPE = 0xE2;\n  private static final int ORIENTATION_TAG_TYPE = 0x0112;\n  private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};\n  // WebP-related\n  // \"RIFF\"\n  private static final int RIFF_HEADER = 0x52494646;\n  // \"WEBP\"\n  private static final int WEBP_HEADER = 0x57454250;\n  // \"VP8\" null.\n  private static final int VP8_HEADER = 0x56503800;\n  private static final int VP8_HEADER_MASK = 0xFFFFFF00;\n  private static final int VP8_HEADER_TYPE_MASK = 0x000000FF;\n  // 'X'\n  private static final int VP8_HEADER_TYPE_EXTENDED = 0x00000058;\n  // 'L'\n  private static final int VP8_HEADER_TYPE_LOSSLESS = 0x0000004C;\n  private static final int WEBP_EXTENDED_ANIMATION_FLAG = 1 << 1;\n  private static final int WEBP_EXTENDED_ALPHA_FLAG = 1 << 4;\n  private static final int WEBP_LOSSLESS_ALPHA_FLAG = 1 << 3;\n  // Avif-related\n  // \"ftyp\"\n  private static final int FTYP_HEADER = 0x66747970;\n  // \"avif\"\n  private static final int AVIF_BRAND = 0x61766966;\n  // \"avis\"\n  private static final int AVIS_BRAND = 0x61766973;\n\n  @NonNull\n  @Override\n  public ImageType getType(@NonNull InputStream is) throws IOException {\n    return getType(new StreamReader(Preconditions.checkNotNull(is)));\n  }\n\n  @NonNull\n  @Override\n  public ImageType getType(@NonNull ByteBuffer byteBuffer) throws IOException {\n    return getType(new ByteBufferReader(Preconditions.checkNotNull(byteBuffer)));\n  }\n\n  @Override\n  public int getOrientation(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    return getOrientation(\n        new StreamReader(Preconditions.checkNotNull(is)),\n        Preconditions.checkNotNull(byteArrayPool));\n  }\n\n  @Override\n  public int getOrientation(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    return getOrientation(\n        new ByteBufferReader(Preconditions.checkNotNull(byteBuffer)),\n        Preconditions.checkNotNull(byteArrayPool));\n  }\n\n  @Override\n  public boolean hasJpegMpf(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    return hasJpegMpf(\n        new StreamReader(Preconditions.checkNotNull(is)),\n        Preconditions.checkNotNull(byteArrayPool));\n  }\n\n  @Override\n  public boolean hasJpegMpf(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    return hasJpegMpf(\n        new ByteBufferReader(Preconditions.checkNotNull(byteBuffer)),\n        Preconditions.checkNotNull(byteArrayPool));\n  }\n\n  private boolean hasJpegMpf(@NonNull Reader reader, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    if (getType(reader) != JPEG) {\n      return false;\n    }\n    int app2SegmentLength = moveToApp2SegmentAndGetLength(reader);\n    while (app2SegmentLength > 0) {\n      byte[] app2Data = byteArrayPool.get(app2SegmentLength, byte[].class);\n      try {\n        boolean hasJpegMpfPreamble = hasJpegMpfPreamble(reader, app2Data, app2SegmentLength);\n        if (hasJpegMpfPreamble) {\n          return true;\n        }\n      } finally {\n        byteArrayPool.put(app2Data);\n      }\n      app2SegmentLength = moveToApp2SegmentAndGetLength(reader);\n    }\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(\n          TAG,\n          \"hasMpf: Failed to parse APP2 segment length, or no APP2 segment with MPF metadata not\"\n              + \" found\");\n    }\n    return false;\n  }\n\n  @NonNull\n  private ImageType getType(Reader reader) throws IOException {\n    try {\n      final int firstTwoBytes = reader.getUInt16();\n      // JPEG.\n      if (firstTwoBytes == EXIF_MAGIC_NUMBER) {\n        return JPEG;\n      }\n\n      final int firstThreeBytes = (firstTwoBytes << 8) | reader.getUInt8();\n      if (firstThreeBytes == GIF_HEADER) {\n        return GIF;\n      }\n\n      final int firstFourBytes = (firstThreeBytes << 8) | reader.getUInt8();\n      // PNG.\n      if (firstFourBytes == PNG_HEADER) {\n        // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha\n        // -color-type\n        reader.skip(25 - 4);\n        try {\n          int alpha = reader.getUInt8();\n          // A RGB indexed PNG can also have transparency. Better safe than sorry!\n          return alpha >= 3 ? PNG_A : PNG;\n        } catch (Reader.EndOfFileException e) {\n          // TODO(b/143917798): Re-enable this logging when dependent tests are fixed.\n          // if (Log.isLoggable(TAG, Log.ERROR)) {\n          //   Log.e(TAG, \"Unexpected EOF, assuming no alpha\", e);\n          // }\n          return PNG;\n        }\n      }\n\n      if (firstFourBytes != RIFF_HEADER) {\n        // Check for AVIF (reads up to 32 bytes). If it is a valid AVIF stream, then the\n        // firstFourBytes will be the size of the FTYP box.\n        return sniffAvif(reader, /* boxSize= */ firstFourBytes);\n      }\n\n      // WebP (reads up to 21 bytes).\n      // See https://developers.google.com/speed/webp/docs/riff_container for details.\n      // Bytes 4 - 7 contain length information. Skip these.\n      reader.skip(4);\n      final int thirdFourBytes = (reader.getUInt16() << 16) | reader.getUInt16();\n      if (thirdFourBytes != WEBP_HEADER) {\n        return UNKNOWN;\n      }\n      final int fourthFourBytes = (reader.getUInt16() << 16) | reader.getUInt16();\n      if ((fourthFourBytes & VP8_HEADER_MASK) != VP8_HEADER) {\n        return UNKNOWN;\n      }\n      if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_EXTENDED) {\n        // Skip some more length bytes and check for transparency/alpha flag.\n        reader.skip(4);\n        short flags = reader.getUInt8();\n        if ((flags & WEBP_EXTENDED_ANIMATION_FLAG) != 0) {\n          return ANIMATED_WEBP;\n        } else if ((flags & WEBP_EXTENDED_ALPHA_FLAG) != 0) {\n          return ImageType.WEBP_A;\n        } else {\n          return ImageType.WEBP;\n        }\n      }\n      if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_LOSSLESS) {\n        // See chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt\n        // for more info.\n        reader.skip(4);\n        short flags = reader.getUInt8();\n        return (flags & WEBP_LOSSLESS_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;\n      }\n      return ImageType.WEBP;\n    } catch (Reader.EndOfFileException e) {\n      // TODO(b/143917798): Re-enable this logging when dependent tests are fixed.\n      // if (Log.isLoggable(TAG, Log.ERROR)) {\n      //   Log.e(TAG, \"Unexpected EOF\", e);\n      // }\n      return UNKNOWN;\n    }\n  }\n\n  /**\n   * Check if the bits look like an AVIF Image. AVIF Specification:\n   * https://aomediacodec.github.io/av1-avif/\n   *\n   * @return AVIF or ANIMATED_AVIF if the first few bytes look like it could be an AVIF Image or an\n   *     animated AVIF Image respectively, UNKNOWN otherwise.\n   */\n  private ImageType sniffAvif(Reader reader, int boxSize) throws IOException {\n    int chunkType = (reader.getUInt16() << 16) | reader.getUInt16();\n    if (chunkType != FTYP_HEADER) {\n      return UNKNOWN;\n    }\n    // majorBrand.\n    int brand = (reader.getUInt16() << 16) | reader.getUInt16();\n    // The overall logic is that, if any of the brands are 'avis', then we can conclude immediately\n    // that it is an animated AVIF image. Otherwise, we conclude after seeing all the brands that if\n    // one of them is 'avif', the it is a still AVIF image.\n    if (brand == AVIS_BRAND) {\n      return ANIMATED_AVIF;\n    }\n    boolean avifBrandSeen = brand == AVIF_BRAND;\n    // Skip the minor version.\n    reader.skip(4);\n    // Check the first five minor brands. While there could theoretically be more than five minor\n    // brands, it is rare in practice. This way we stop the loop from running several times on a\n    // blob that just happened to look like an ftyp box.\n    int sizeRemaining = boxSize - 16;\n    if (sizeRemaining % 4 == 0) {\n      for (int i = 0; i < 5 && sizeRemaining > 0; ++i, sizeRemaining -= 4) {\n        brand = (reader.getUInt16() << 16) | reader.getUInt16();\n        if (brand == AVIS_BRAND) {\n          return ANIMATED_AVIF;\n        } else if (brand == AVIF_BRAND) {\n          avifBrandSeen = true;\n        }\n      }\n    }\n    return avifBrandSeen ? AVIF : UNKNOWN;\n  }\n\n  /**\n   * Parse the orientation from the image header. If it doesn't handle this image type (or this is\n   * not an image) it will return a default value rather than throwing an exception.\n   *\n   * @return The exif orientation if present or -1 if the header couldn't be parsed or doesn't\n   *     contain an orientation\n   */\n  private int getOrientation(Reader reader, ArrayPool byteArrayPool) throws IOException {\n    try {\n      final int magicNumber = reader.getUInt16();\n\n      if (!handles(magicNumber)) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Parser doesn't handle magic number: \" + magicNumber);\n        }\n        return UNKNOWN_ORIENTATION;\n      } else {\n        int exifSegmentLength = moveToExifSegmentAndGetLength(reader);\n        if (exifSegmentLength == -1) {\n          if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(TAG, \"Failed to parse exif segment length, or exif segment not found\");\n          }\n          return UNKNOWN_ORIENTATION;\n        }\n\n        byte[] exifData = byteArrayPool.get(exifSegmentLength, byte[].class);\n        try {\n          return parseExifSegment(reader, exifData, exifSegmentLength);\n        } finally {\n          byteArrayPool.put(exifData);\n        }\n      }\n    } catch (Reader.EndOfFileException e) {\n      // TODO(b/143917798): Re-enable this logging when dependent tests are fixed.\n      // if (Log.isLoggable(TAG, Log.ERROR)) {\n      //   Log.e(TAG, \"Unexpected EOF\", e);\n      // }\n      return UNKNOWN_ORIENTATION;\n    }\n  }\n\n  private int parseExifSegment(Reader reader, byte[] tempArray, int exifSegmentLength)\n      throws IOException {\n    int read = reader.read(tempArray, exifSegmentLength);\n    if (read != exifSegmentLength) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"Unable to read exif segment data\"\n                + \", length: \"\n                + exifSegmentLength\n                + \", actually read: \"\n                + read);\n      }\n      return UNKNOWN_ORIENTATION;\n    }\n\n    boolean hasJpegExifPreamble = hasJpegExifPreamble(tempArray, exifSegmentLength);\n    if (hasJpegExifPreamble) {\n      return parseExifSegment(new RandomAccessReader(tempArray, exifSegmentLength));\n    } else {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Missing jpeg exif preamble\");\n      }\n      return UNKNOWN_ORIENTATION;\n    }\n  }\n\n  private boolean hasJpegExifPreamble(byte[] exifData, int exifSegmentLength) {\n    return hasMatchingBytes(exifData, exifSegmentLength, JPEG_EXIF_SEGMENT_PREAMBLE_BYTES);\n  }\n\n  private boolean hasMatchingBytes(byte[] bytes, int byteLength, byte[] bytesToMatch) {\n    boolean result = bytes != null && bytesToMatch != null && byteLength > bytesToMatch.length;\n    if (result) {\n      for (int i = 0; i < bytesToMatch.length; i++) {\n        if (bytes[i] != bytesToMatch[i]) {\n          result = false;\n          break;\n        }\n      }\n    }\n    return result;\n  }\n\n  /**\n   * Moves reader to the start of the exif segment and returns the length of the exif segment or\n   * {@code -1} if no exif segment is found.\n   */\n  private int moveToExifSegmentAndGetLength(Reader reader) throws IOException {\n    return moveToSegmentAndGetLength(reader, EXIF_SEGMENT_TYPE);\n  }\n\n  /**\n   * Returns whether the reader, set at the beginning of the APP2 segment past the length bytes,\n   * contains multi-picture format (MPF) data.\n   *\n   * @param reader must be set at the start of an APP2 segment, past the APP2 label and length\n   *     bytes.\n   * @param tempArray for storing temporary array. Must be at least the size of {@code\n   *     app2SegmentLength}.\n   * @param app2SegmentLength the length of the APP2 segment.\n   * @throws IOException if an EOF is reached before anything was read.\n   */\n  private boolean hasJpegMpfPreamble(Reader reader, byte[] tempArray, int app2SegmentLength)\n      throws IOException {\n    int read = reader.read(tempArray, app2SegmentLength);\n    if (read != app2SegmentLength) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"Unable to read APP2 segment data\"\n                + \", length: \"\n                + app2SegmentLength\n                + \", actually read: \"\n                + read);\n      }\n      return false;\n    }\n    return hasMatchingBytes(tempArray, app2SegmentLength, JPEG_MPF_SEGMENT_PREAMBLE_BYTES);\n  }\n\n  private int moveToApp2SegmentAndGetLength(Reader reader) throws IOException {\n    return moveToSegmentAndGetLength(reader, APP2_SEGMENT_TYPE);\n  }\n\n  /**\n   * Moves reader to the start of the segment identified by the segment type (e.g., \"0xE1\" for APP1\n   * and returns the length of the exif segment or {@code -1} if no segment of that type is found.\n   */\n  private int moveToSegmentAndGetLength(Reader reader, int requestedSegmentType)\n      throws IOException {\n    while (true) {\n      short segmentId = reader.getUInt8();\n      if (segmentId != SEGMENT_START_ID) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Unknown segmentId=\" + segmentId);\n        }\n        return -1;\n      }\n\n      short segmentType = reader.getUInt8();\n      if (segmentType == SEGMENT_SOS) {\n        return -1;\n      } else if (segmentType == MARKER_EOI) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Found MARKER_EOI in \" + requestedSegmentType + \" segment\");\n        }\n        return -1;\n      }\n\n      int segmentLength = reader.getUInt16();\n      // A segment includes the bytes that specify its length.\n      int segmentContentsLength = segmentLength - 2;\n      if (segmentType != requestedSegmentType) {\n        long skipped = reader.skip(segmentContentsLength);\n        if (skipped != segmentContentsLength) {\n          if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(\n                TAG,\n                \"Unable to skip enough data\"\n                    + \", type: \"\n                    + segmentType\n                    + \", wanted to skip: \"\n                    + segmentContentsLength\n                    + \", but actually skipped: \"\n                    + skipped);\n          }\n          return -1;\n        }\n      } else {\n        return segmentContentsLength;\n      }\n    }\n  }\n\n  private static int parseExifSegment(RandomAccessReader segmentData) {\n    final int headerOffsetSize = JPEG_EXIF_SEGMENT_PREAMBLE.length();\n\n    short byteOrderIdentifier = segmentData.getInt16(headerOffsetSize);\n    final ByteOrder byteOrder;\n    switch (byteOrderIdentifier) {\n      case MOTOROLA_TIFF_MAGIC_NUMBER:\n        byteOrder = ByteOrder.BIG_ENDIAN;\n        break;\n      case INTEL_TIFF_MAGIC_NUMBER:\n        byteOrder = ByteOrder.LITTLE_ENDIAN;\n        break;\n      default:\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Unknown endianness = \" + byteOrderIdentifier);\n        }\n        byteOrder = ByteOrder.BIG_ENDIAN;\n        break;\n    }\n\n    segmentData.order(byteOrder);\n\n    int firstIfdOffset = segmentData.getInt32(headerOffsetSize + 4) + headerOffsetSize;\n    int tagCount = segmentData.getInt16(firstIfdOffset);\n    for (int i = 0; i < tagCount; i++) {\n      final int tagOffset = calcTagOffset(firstIfdOffset, i);\n\n      final int tagType = segmentData.getInt16(tagOffset);\n      // We only want orientation.\n      if (tagType != ORIENTATION_TAG_TYPE) {\n        continue;\n      }\n\n      final int formatCode = segmentData.getInt16(tagOffset + 2);\n      // 12 is max format code.\n      if (formatCode < 1 || formatCode > 12) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Got invalid format code = \" + formatCode);\n        }\n        continue;\n      }\n\n      final int componentCount = segmentData.getInt32(tagOffset + 4);\n      if (componentCount < 0) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Negative tiff component count\");\n        }\n        continue;\n      }\n\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"Got tagIndex=\"\n                + i\n                + \" tagType=\"\n                + tagType\n                + \" formatCode=\"\n                + formatCode\n                + \" componentCount=\"\n                + componentCount);\n      }\n\n      final int byteCount = componentCount + BYTES_PER_FORMAT[formatCode];\n      if (byteCount > 4) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Got byte count > 4, not orientation, continuing, formatCode=\" + formatCode);\n        }\n        continue;\n      }\n\n      final int tagValueOffset = tagOffset + 8;\n      if (tagValueOffset < 0 || tagValueOffset > segmentData.length()) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Illegal tagValueOffset=\" + tagValueOffset + \" tagType=\" + tagType);\n        }\n        continue;\n      }\n\n      if (byteCount < 0 || tagValueOffset + byteCount > segmentData.length()) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Illegal number of bytes for TI tag data tagType=\" + tagType);\n        }\n        continue;\n      }\n\n      // assume componentCount == 1 && fmtCode == 3\n      return segmentData.getInt16(tagValueOffset);\n    }\n\n    return -1;\n  }\n\n  private static int calcTagOffset(int ifdOffset, int tagIndex) {\n    return ifdOffset + 2 + 12 * tagIndex;\n  }\n\n  private static boolean handles(int imageMagicNumber) {\n    return (imageMagicNumber & EXIF_MAGIC_NUMBER) == EXIF_MAGIC_NUMBER\n        || imageMagicNumber == MOTOROLA_TIFF_MAGIC_NUMBER\n        || imageMagicNumber == INTEL_TIFF_MAGIC_NUMBER;\n  }\n\n  private static final class RandomAccessReader {\n    private final ByteBuffer data;\n\n    RandomAccessReader(byte[] data, int length) {\n      this.data = (ByteBuffer) ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).limit(length);\n    }\n\n    void order(ByteOrder byteOrder) {\n      this.data.order(byteOrder);\n    }\n\n    int length() {\n      return data.remaining();\n    }\n\n    int getInt32(int offset) {\n      return isAvailable(offset, 4) ? data.getInt(offset) : -1;\n    }\n\n    short getInt16(int offset) {\n      return isAvailable(offset, 2) ? data.getShort(offset) : -1;\n    }\n\n    private boolean isAvailable(int offset, int byteSize) {\n      return data.remaining() - offset >= byteSize;\n    }\n  }\n\n  private interface Reader {\n\n    /**\n     * Reads and returns a 8-bit unsigned integer.\n     *\n     * <p>Throws an {@link EndOfFileException} if an EOF is reached.\n     */\n    short getUInt8() throws IOException;\n\n    /**\n     * Reads and returns a 16-bit unsigned integer.\n     *\n     * <p>Throws an {@link EndOfFileException} if an EOF is reached.\n     */\n    int getUInt16() throws IOException;\n\n    /**\n     * Reads and returns a byte array.\n     *\n     * <p>Throws an {@link EndOfFileException} if an EOF is reached before anything was read.\n     */\n    int read(byte[] buffer, int byteCount) throws IOException;\n\n    long skip(long total) throws IOException;\n\n    // TODO(timurrrr): Stop inheriting from IOException, and make sure all attempts to read from\n    //   a Reader correctly handle EOFs.\n    final class EndOfFileException extends IOException {\n      private static final long serialVersionUID = 1L;\n\n      EndOfFileException() {\n        super(\"Unexpectedly reached end of a file\");\n      }\n    }\n  }\n\n  private static final class ByteBufferReader implements Reader {\n\n    private final ByteBuffer byteBuffer;\n\n    ByteBufferReader(ByteBuffer byteBuffer) {\n      this.byteBuffer = byteBuffer;\n      byteBuffer.order(ByteOrder.BIG_ENDIAN);\n    }\n\n    @Override\n    public short getUInt8() throws EndOfFileException {\n      if (byteBuffer.remaining() < 1) {\n        throw new EndOfFileException();\n      }\n      return (short) (byteBuffer.get() & 0xFF);\n    }\n\n    @Override\n    public int getUInt16() throws EndOfFileException {\n      return ((int) getUInt8() << 8) | getUInt8();\n    }\n\n    @Override\n    public int read(byte[] buffer, int byteCount) {\n      int toRead = Math.min(byteCount, byteBuffer.remaining());\n      if (toRead == 0) {\n        return -1;\n      }\n      byteBuffer.get(buffer, 0 /*dstOffset*/, toRead);\n      return toRead;\n    }\n\n    @Override\n    public long skip(long total) {\n      int toSkip = (int) Math.min(byteBuffer.remaining(), total);\n      byteBuffer.position(byteBuffer.position() + toSkip);\n      return toSkip;\n    }\n  }\n\n  private static final class StreamReader implements Reader {\n    private final InputStream is;\n\n    // Motorola / big endian byte order.\n    StreamReader(InputStream is) {\n      this.is = is;\n    }\n\n    @Override\n    public short getUInt8() throws IOException {\n      int readResult = is.read();\n      if (readResult == -1) {\n        throw new EndOfFileException();\n      }\n\n      return (short) readResult;\n    }\n\n    @Override\n    public int getUInt16() throws IOException {\n      return ((int) getUInt8() << 8) | getUInt8();\n    }\n\n    @Override\n    public int read(byte[] buffer, int byteCount) throws IOException {\n      int numBytesRead = 0;\n      int lastReadResult = 0;\n      while (numBytesRead < byteCount\n          && (lastReadResult = is.read(buffer, numBytesRead, byteCount - numBytesRead)) != -1) {\n        numBytesRead += lastReadResult;\n      }\n\n      if (numBytesRead == 0 && lastReadResult == -1) {\n        throw new EndOfFileException();\n      }\n\n      return numBytesRead;\n    }\n\n    @Override\n    public long skip(long total) throws IOException {\n      if (total < 0) {\n        return 0;\n      }\n\n      long toSkip = total;\n      while (toSkip > 0) {\n        long skipped = is.skip(toSkip);\n        if (skipped > 0) {\n          toSkip -= skipped;\n        } else {\n          // Skip has no specific contract as to what happens when you reach the end of\n          // the stream. To differentiate between temporarily not having more data and\n          // having finished the stream, we read a single byte when we fail to skip any\n          // amount of data.\n          int testEofByte = is.read();\n          if (testEofByte == -1) {\n            break;\n          } else {\n            toSkip--;\n          }\n        }\n      }\n      return total - toSkip;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/DownsampleStrategy.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.os.Build;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.util.Synthetic;\n\n/**\n * Indicates the algorithm to use when downsampling images.\n *\n * <p>{@code DownsampleStrategy} does not provide any guarantees about output sizes. Behavior will\n * differ depending on the {@link com.bumptech.glide.load.ResourceDecoder} using the strategy and\n * the version of Android the code runs on. Use {@code DownsampleStrategy} as an optimization to\n * improve memory efficiency only. If you need a particular size or shape output, use an {@link\n * com.bumptech.glide.load.Transformation} either instead or in addition to a {@code\n * DownsampleStrategy}.\n *\n * <p>Some differences between versions of Android and {@link\n * com.bumptech.glide.load.ResourceDecoder}s are listed below, but the list is not comprehensive\n * because {@link DownsampleStrategy} only controls its output scale value, not how that output\n * value is used.\n *\n * <p>On some versions of Android, precise scaling is not possible. In those cases, the strategies\n * can only pick between downsampling to between 1x the requested size and 2x the requested size and\n * between 0.5x the requested size and 1x the requested size because only power of two downsampling\n * is supported. To preserve the potential for a {@link com.bumptech.glide.load.Transformation} to\n * scale precisely without a loss in quality, all but {@link #AT_MOST} will prefer to downsample to\n * between 1x and 2x the requested size.\n */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic abstract class DownsampleStrategy {\n\n  /**\n   * Downsamples so the image's smallest dimension is between the given dimensions and 2x the given\n   * dimensions, with no size restrictions on the image's largest dimension.\n   *\n   * <p>Does not upscale if the requested dimensions are larger than the original dimensions.\n   */\n  public static final DownsampleStrategy AT_LEAST = new AtLeast();\n\n  /**\n   * Downsamples so the image's largest dimension is between 1/2 the given dimensions and the given\n   * dimensions, with no restrictions on the image's smallest dimension.\n   *\n   * <p>Does not upscale if the requested dimensions are larger than the original dimensions.\n   */\n  public static final DownsampleStrategy AT_MOST = new AtMost();\n\n  /**\n   * Scales, maintaining the original aspect ratio, so that one of the image's dimensions is exactly\n   * equal to the requested size and the other dimension is less than or equal to the requested\n   * size.\n   *\n   * <p>This method will upscale if the requested width and height are greater than the source width\n   * and height. To avoid upscaling, use {@link #AT_LEAST}, {@link #AT_MOST} or {@link\n   * #CENTER_INSIDE}.\n   *\n   * <p>On pre-KitKat devices, {@code FIT_CENTER} will downsample by a power of two only so that one\n   * of the image's dimensions is greater than or equal to the requested size. No guarantees are\n   * made about the second dimensions. This is <em>NOT</em> the same as {@link #AT_LEAST} because\n   * only one dimension, not both, are greater than or equal to the requested dimensions, the other\n   * may be smaller.\n   */\n  public static final DownsampleStrategy FIT_CENTER = new FitCenter();\n\n  /** Identical to {@link #FIT_CENTER}, but never upscales. */\n  public static final DownsampleStrategy CENTER_INSIDE = new CenterInside();\n\n  /**\n   * Scales, maintaining the original aspect ratio, so that one of the image's dimensions is exactly\n   * equal to the requested size and the other dimension is greater than or equal to the requested\n   * size.\n   *\n   * <p>This method will upscale if the requested width and height are greater than the source width\n   * and height. To avoid upscaling, use {@link #AT_LEAST}, {@link #AT_MOST}, or {@link\n   * #CENTER_INSIDE}.\n   *\n   * <p>On pre-KitKat devices, {@link Downsampler} treats this as equivalent to {@link #AT_LEAST}\n   * because only power of two downsampling can be used.\n   */\n  public static final DownsampleStrategy CENTER_OUTSIDE = new CenterOutside();\n\n  /** Performs no downsampling or scaling. */\n  public static final DownsampleStrategy NONE = new None();\n\n  /** Default strategy, currently {@link #CENTER_OUTSIDE}. */\n  public static final DownsampleStrategy DEFAULT = CENTER_OUTSIDE;\n\n  /**\n   * Indicates the {@link com.bumptech.glide.load.resource.bitmap.DownsampleStrategy} option that\n   * will be used to calculate the sample size to use to downsample an image given the original and\n   * target dimensions of the image.\n   */\n  // The exact String value here is retained to avoid breaking cache keys for images that were\n  // loaded with older versions of Glide.\n  public static final Option<DownsampleStrategy> OPTION =\n      Option.memory(\n          \"com.bumptech.glide.load.resource.bitmap.Downsampler.DownsampleStrategy\", DEFAULT);\n\n  @Synthetic\n  static final boolean IS_BITMAP_FACTORY_SCALING_SUPPORTED =\n      Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;\n\n  /**\n   * Returns a float (0, +infinity) indicating a scale factor to apply to the source width and\n   * height when displayed in the requested width and height.\n   *\n   * <p>The returned scale factor will be split into a power of two sample size applied via {@link\n   * android.graphics.BitmapFactory.Options#inSampleSize} and a float scale factor applied after\n   * downsampling via {@link android.graphics.BitmapFactory.Options#inTargetDensity} and {@link\n   * android.graphics.BitmapFactory.Options#inDensity}. Because of rounding errors the scale factor\n   * may not be applied precisely.\n   *\n   * <p>The float scaling factor will only be applied on KitKat+. Prior to KitKat, only the power of\n   * two downsampling will be applied.\n   *\n   * @param sourceWidth The width in pixels of the image to be downsampled.\n   * @param sourceHeight The height in pixels of the image to be downsampled.\n   * @param requestedWidth The width in pixels of the view/target the image will be displayed in.\n   * @param requestedHeight The height in pixels of the view/target the image will be displayed in.\n   */\n  public abstract float getScaleFactor(\n      int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight);\n\n  /**\n   * Returns a non-null {@link SampleSizeRounding} to use to resolve rounding errors and conflicts\n   * between scaling for the width and the height of the image.\n   *\n   * @param sourceWidth The width in pixels of the image to be downsampled.\n   * @param sourceHeight The height in pixels of the image to be downsampled.\n   * @param requestedWidth The width in pixels of the view/target the image will be displayed in.\n   * @param requestedHeight The height in pixels of the view/target the image will be displayed in.\n   */\n  public abstract SampleSizeRounding getSampleSizeRounding(\n      int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight);\n\n  private static class FitCenter extends DownsampleStrategy {\n\n    @Synthetic\n    FitCenter() {}\n\n    @Override\n    public float getScaleFactor(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      if (IS_BITMAP_FACTORY_SCALING_SUPPORTED) {\n        float widthPercentage = requestedWidth / (float) sourceWidth;\n        float heightPercentage = requestedHeight / (float) sourceHeight;\n\n        return Math.min(widthPercentage, heightPercentage);\n      } else {\n        // Similar to AT_LEAST, but only require one dimension or the other to be >= requested\n        // rather than both.\n        int maxIntegerFactor =\n            Math.max(sourceHeight / requestedHeight, sourceWidth / requestedWidth);\n        return maxIntegerFactor == 0 ? 1f : 1f / Integer.highestOneBit(maxIntegerFactor);\n      }\n    }\n\n    @Override\n    public SampleSizeRounding getSampleSizeRounding(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      if (IS_BITMAP_FACTORY_SCALING_SUPPORTED) {\n        return SampleSizeRounding.QUALITY;\n      } else {\n        // TODO: This doesn't seem right, but otherwise we can skip a sample size because QUALITY\n        // prefers the smaller of the width and height scale factor. MEMORY is a hack that\n        // lets us prefer the larger of the two.\n        return SampleSizeRounding.MEMORY;\n      }\n    }\n  }\n\n  private static class CenterOutside extends DownsampleStrategy {\n\n    @Synthetic\n    CenterOutside() {}\n\n    @Override\n    public float getScaleFactor(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      float widthPercentage = requestedWidth / (float) sourceWidth;\n      float heightPercentage = requestedHeight / (float) sourceHeight;\n      return Math.max(widthPercentage, heightPercentage);\n    }\n\n    @Override\n    public SampleSizeRounding getSampleSizeRounding(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      return SampleSizeRounding.QUALITY;\n    }\n  }\n\n  private static class AtLeast extends DownsampleStrategy {\n\n    @Synthetic\n    AtLeast() {}\n\n    @Override\n    public float getScaleFactor(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      int minIntegerFactor = Math.min(sourceHeight / requestedHeight, sourceWidth / requestedWidth);\n      return minIntegerFactor == 0 ? 1f : 1f / Integer.highestOneBit(minIntegerFactor);\n    }\n\n    @Override\n    public SampleSizeRounding getSampleSizeRounding(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      return SampleSizeRounding.QUALITY;\n    }\n  }\n\n  private static class AtMost extends DownsampleStrategy {\n\n    @Synthetic\n    AtMost() {}\n\n    @Override\n    public float getScaleFactor(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      int maxIntegerFactor =\n          (int)\n              Math.ceil(\n                  Math.max(\n                      sourceHeight / (float) requestedHeight,\n                      sourceWidth / (float) requestedWidth));\n      int lesserOrEqualSampleSize = Math.max(1, Integer.highestOneBit(maxIntegerFactor));\n      int greaterOrEqualSampleSize =\n          lesserOrEqualSampleSize << (lesserOrEqualSampleSize < maxIntegerFactor ? 1 : 0);\n      return 1f / greaterOrEqualSampleSize;\n    }\n\n    @Override\n    public SampleSizeRounding getSampleSizeRounding(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      return SampleSizeRounding.MEMORY;\n    }\n  }\n\n  private static class None extends DownsampleStrategy {\n\n    @Synthetic\n    None() {}\n\n    @Override\n    public float getScaleFactor(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      return 1f;\n    }\n\n    @Override\n    public SampleSizeRounding getSampleSizeRounding(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      return SampleSizeRounding.QUALITY;\n    }\n  }\n\n  private static class CenterInside extends DownsampleStrategy {\n\n    @Synthetic\n    CenterInside() {}\n\n    @Override\n    public float getScaleFactor(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n\n      return Math.min(\n          1.f,\n          FIT_CENTER.getScaleFactor(sourceWidth, sourceHeight, requestedWidth, requestedHeight));\n    }\n\n    @Override\n    public SampleSizeRounding getSampleSizeRounding(\n        int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {\n      return getScaleFactor(sourceWidth, sourceHeight, requestedWidth, requestedHeight) == 1.f\n          ? SampleSizeRounding.QUALITY\n          : FIT_CENTER.getSampleSizeRounding(\n              sourceWidth, sourceHeight, requestedWidth, requestedHeight);\n    }\n  }\n\n  /**\n   * Indicates whether to prefer to prefer downsampling or scaling to prefer lower memory usage or\n   * higher quality.\n   */\n  public enum SampleSizeRounding {\n    /**\n     * Prefer to round the sample size up so that the image is downsampled to smaller than the\n     * requested size to use less memory.\n     */\n    MEMORY,\n    /**\n     * Prefer to round the sample size down so that the image is downsampled to larger than the\n     * requested size to maintain quality at the expense of extra memory usage.\n     */\n    QUALITY,\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.annotation.TargetApi;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\nimport android.graphics.BitmapFactory;\nimport android.graphics.ColorSpace;\nimport android.os.Build;\nimport android.os.ParcelFileDescriptor;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.PreferredColorSpace;\nimport com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.SampleSizeRounding;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.LogTime;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Util;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.EnumSet;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Queue;\nimport java.util.Set;\n\n/**\n * Downsamples, decodes, and rotates images according to their exif orientation using {@link\n * BitmapFactory}.\n */\npublic final class Downsampler {\n  static final String TAG = \"Downsampler\";\n\n  /**\n   * Indicates the {@link com.bumptech.glide.load.DecodeFormat} that will be used in conjunction\n   * with the image format to determine the {@link android.graphics.Bitmap.Config} to provide to\n   * {@link android.graphics.BitmapFactory.Options#inPreferredConfig} when decoding the image.\n   */\n  public static final Option<DecodeFormat> DECODE_FORMAT =\n      Option.memory(\n          \"com.bumptech.glide.load.resource.bitmap.Downsampler.DecodeFormat\", DecodeFormat.DEFAULT);\n\n  /**\n   * Sets the {@link PreferredColorSpace} that will be used along with the version of Android and\n   * color space of the requested image to determine the final color space used to decode the image.\n   *\n   * <p>Refer to {@link PreferredColorSpace} for details on how this option works and its various\n   * limitations.\n   */\n  public static final Option<PreferredColorSpace> PREFERRED_COLOR_SPACE =\n      Option.memory(\"com.bumptech.glide.load.resource.bitmap.Downsampler.PreferredColorSpace\");\n\n  /**\n   * Indicates the {@link com.bumptech.glide.load.resource.bitmap.DownsampleStrategy} option that\n   * will be used to calculate the sample size to use to downsample an image given the original and\n   * target dimensions of the image.\n   *\n   * @deprecated Use {@link DownsampleStrategy#OPTION} directly instead.\n   */\n  @Deprecated\n  public static final Option<DownsampleStrategy> DOWNSAMPLE_STRATEGY = DownsampleStrategy.OPTION;\n\n  /**\n   * Ensure that the size of the bitmap is fixed to the requested width and height of the resource\n   * from the caller. The final resource dimensions may differ from the requested width and height,\n   * and thus setting this to true may result in the bitmap size differing from the resource\n   * dimensions.\n   *\n   * <p>This can be used as a performance optimization for KitKat and above by fixing the size of\n   * the bitmap for a collection of requested resources so that the bitmap pool will not need to\n   * allocate new bitmaps for images of different sizes.\n   */\n  // Public API\n  @SuppressWarnings(\"WeakerAccess\")\n  public static final Option<Boolean> FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS =\n      Option.memory(\"com.bumptech.glide.load.resource.bitmap.Downsampler.FixBitmapSize\", false);\n\n  /**\n   * Indicates that it's safe or unsafe to decode {@link Bitmap}s with {@link\n   * Bitmap.Config#HARDWARE}.\n   *\n   * <p>Callers should almost never set this value to {@code true} manually. Glide will already do\n   * so when Glide believes it's safe to do (when no transformations are applied). Instead, callers\n   * can set this value to {@code false} to prevent Glide from decoding hardware bitmaps if Glide is\n   * unable to detect that hardware bitmaps are unsafe. For example, you should set this to {@code\n   * false} if you plan to draw it to a software {@link android.graphics.Canvas} or if you plan to\n   * inspect the {@link Bitmap}s pixels with {@link Bitmap#getPixel(int, int)} or {@link\n   * Bitmap#getPixels(int[], int, int, int, int, int, int)}.\n   *\n   * <p>Callers can disable hardware {@link Bitmap}s for all loads using {@link\n   * com.bumptech.glide.GlideBuilder#setDefaultRequestOptions(RequestOptions)}.\n   *\n   * <p>This option is ignored unless we're on Android O+.\n   */\n  public static final Option<Boolean> ALLOW_HARDWARE_CONFIG =\n      Option.memory(\n          \"com.bumptech.glide.load.resource.bitmap.Downsampler.AllowHardwareDecode\", false);\n\n  private static final String WBMP_MIME_TYPE = \"image/vnd.wap.wbmp\";\n  private static final String ICO_MIME_TYPE = \"image/x-ico\";\n  private static final Set<String> NO_DOWNSAMPLE_PRE_N_MIME_TYPES =\n      Collections.unmodifiableSet(new HashSet<>(Arrays.asList(WBMP_MIME_TYPE, ICO_MIME_TYPE)));\n  private static final DecodeCallbacks EMPTY_CALLBACKS =\n      new DecodeCallbacks() {\n        @Override\n        public void onObtainBounds() {\n          // Do nothing.\n        }\n\n        @Override\n        public void onDecodeComplete(BitmapPool bitmapPool, Bitmap downsampled) {\n          // Do nothing.\n        }\n      };\n  private static final Set<ImageHeaderParser.ImageType> TYPES_THAT_USE_POOL_PRE_KITKAT =\n      Collections.unmodifiableSet(\n          EnumSet.of(\n              ImageHeaderParser.ImageType.JPEG,\n              ImageHeaderParser.ImageType.PNG_A,\n              ImageHeaderParser.ImageType.PNG));\n  private static final Queue<BitmapFactory.Options> OPTIONS_QUEUE = Util.createQueue(0);\n\n  private final BitmapPool bitmapPool;\n  private final DisplayMetrics displayMetrics;\n  private final ArrayPool byteArrayPool;\n  private final List<ImageHeaderParser> parsers;\n  private final HardwareConfigState hardwareConfigState = HardwareConfigState.getInstance();\n\n  public Downsampler(\n      List<ImageHeaderParser> parsers,\n      DisplayMetrics displayMetrics,\n      BitmapPool bitmapPool,\n      ArrayPool byteArrayPool) {\n    this.parsers = parsers;\n    this.displayMetrics = Preconditions.checkNotNull(displayMetrics);\n    this.bitmapPool = Preconditions.checkNotNull(bitmapPool);\n    this.byteArrayPool = Preconditions.checkNotNull(byteArrayPool);\n  }\n\n  public boolean handles(@SuppressWarnings(\"unused\") InputStream is) {\n    // We expect Downsampler to handle any available type Android supports.\n    return true;\n  }\n\n  public boolean handles(@SuppressWarnings(\"unused\") ByteBuffer byteBuffer) {\n    // We expect downsampler to handle any available type Android supports.\n    return true;\n  }\n\n  public boolean handles(@SuppressWarnings(\"unused\") ParcelFileDescriptor source) {\n    return ParcelFileDescriptorRewinder.isSupported();\n  }\n\n  /**\n   * Returns a Bitmap decoded from the given {@link InputStream} that is rotated to match any EXIF\n   * data present in the stream and that is downsampled according to the given dimensions and any\n   * provided {@link com.bumptech.glide.load.resource.bitmap.DownsampleStrategy} option.\n   *\n   * @see #decode(InputStream, int, int, Options, DecodeCallbacks)\n   */\n  public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight, Options options)\n      throws IOException {\n    return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS);\n  }\n\n  /**\n   * Identical to {@link #decode(InputStream, int, int, Options)}, except that it accepts a {@link\n   * ByteBuffer} in place of an {@link InputStream}.\n   */\n  public Resource<Bitmap> decode(\n      ByteBuffer buffer, int requestedWidth, int requestedHeight, Options options)\n      throws IOException {\n    return decode(\n        new ImageReader.ByteBufferReader(buffer, parsers, byteArrayPool),\n        requestedWidth,\n        requestedHeight,\n        options,\n        EMPTY_CALLBACKS);\n  }\n\n  /**\n   * Returns a Bitmap decoded from the given {@link InputStream} that is rotated to match any EXIF\n   * data present in the stream and that is downsampled according to the given dimensions and any\n   * provided {@link com.bumptech.glide.load.resource.bitmap.DownsampleStrategy} option.\n   *\n   * <p>If a Bitmap is present in the {@link\n   * com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} whose dimensions exactly match those\n   * of the image for the given InputStream is available, the operation is much less expensive in\n   * terms of memory.\n   *\n   * @param is An {@link InputStream} to the data for the image.\n   * @param requestedWidth The width the final image should be close to.\n   * @param requestedHeight The height the final image should be close to.\n   * @param options A set of options that may contain one or more supported options that influence\n   *     how a Bitmap will be decoded from the given stream.\n   * @param callbacks A set of callbacks allowing callers to optionally respond to various\n   *     significant events during the decode process.\n   * @return A new bitmap containing the image from the given InputStream, or recycle if recycle is\n   *     not null.\n   */\n  public Resource<Bitmap> decode(\n      InputStream is,\n      int requestedWidth,\n      int requestedHeight,\n      Options options,\n      DecodeCallbacks callbacks)\n      throws IOException {\n    return decode(\n        new ImageReader.InputStreamImageReader(is, parsers, byteArrayPool),\n        requestedWidth,\n        requestedHeight,\n        options,\n        callbacks);\n  }\n\n  @VisibleForTesting\n  void decode(byte[] bytes, int requestedWidth, int requestedHeight, Options options)\n      throws IOException {\n    decode(\n        new ImageReader.ByteArrayReader(bytes, parsers, byteArrayPool),\n        requestedWidth,\n        requestedHeight,\n        options,\n        EMPTY_CALLBACKS);\n  }\n\n  @VisibleForTesting\n  void decode(File file, int requestedWidth, int requestedHeight, Options options)\n      throws IOException {\n    decode(\n        new ImageReader.FileReader(file, parsers, byteArrayPool),\n        requestedWidth,\n        requestedHeight,\n        options,\n        EMPTY_CALLBACKS);\n  }\n\n  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)\n  public Resource<Bitmap> decode(\n      ParcelFileDescriptor parcelFileDescriptor, int outWidth, int outHeight, Options options)\n      throws IOException {\n    return decode(\n        new ImageReader.ParcelFileDescriptorImageReader(\n            parcelFileDescriptor, parsers, byteArrayPool),\n        outWidth,\n        outHeight,\n        options,\n        EMPTY_CALLBACKS);\n  }\n\n  private Resource<Bitmap> decode(\n      ImageReader imageReader,\n      int requestedWidth,\n      int requestedHeight,\n      Options options,\n      DecodeCallbacks callbacks)\n      throws IOException {\n    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);\n    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();\n    bitmapFactoryOptions.inTempStorage = bytesForOptions;\n\n    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);\n    PreferredColorSpace preferredColorSpace = options.get(PREFERRED_COLOR_SPACE);\n    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);\n    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);\n    boolean isHardwareConfigAllowed =\n        options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);\n\n    try {\n      Bitmap result =\n          decodeFromWrappedStreams(\n              imageReader,\n              bitmapFactoryOptions,\n              downsampleStrategy,\n              decodeFormat,\n              preferredColorSpace,\n              isHardwareConfigAllowed,\n              requestedWidth,\n              requestedHeight,\n              fixBitmapToRequestedDimensions,\n              callbacks);\n      return BitmapResource.obtain(result, bitmapPool);\n    } finally {\n      releaseOptions(bitmapFactoryOptions);\n      byteArrayPool.put(bytesForOptions);\n    }\n  }\n\n  private Bitmap decodeFromWrappedStreams(\n      ImageReader imageReader,\n      BitmapFactory.Options options,\n      DownsampleStrategy downsampleStrategy,\n      DecodeFormat decodeFormat,\n      PreferredColorSpace preferredColorSpace,\n      boolean isHardwareConfigAllowed,\n      int requestedWidth,\n      int requestedHeight,\n      boolean fixBitmapToRequestedDimensions,\n      DecodeCallbacks callbacks)\n      throws IOException {\n    long startTime = LogTime.getLogTime();\n\n    int[] sourceDimensions = getDimensions(imageReader, options, callbacks, bitmapPool);\n    int sourceWidth = sourceDimensions[0];\n    int sourceHeight = sourceDimensions[1];\n    String sourceMimeType = options.outMimeType;\n\n    // If we failed to obtain the image dimensions, we may end up with an incorrectly sized Bitmap,\n    // so we want to use a mutable Bitmap type. One way this can happen is if the image header is so\n    // large (10mb+) that our attempt to use inJustDecodeBounds fails and we're forced to decode the\n    // full size image.\n    if (sourceWidth == -1 || sourceHeight == -1) {\n      isHardwareConfigAllowed = false;\n    }\n\n    int orientation = imageReader.getImageOrientation();\n    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);\n    boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);\n\n    int targetWidth =\n        requestedWidth == Target.SIZE_ORIGINAL\n            ? (isRotationRequired(degreesToRotate) ? sourceHeight : sourceWidth)\n            : requestedWidth;\n    int targetHeight =\n        requestedHeight == Target.SIZE_ORIGINAL\n            ? (isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight)\n            : requestedHeight;\n\n    ImageType imageType = imageReader.getImageType();\n\n    calculateScaling(\n        imageType,\n        imageReader,\n        callbacks,\n        bitmapPool,\n        downsampleStrategy,\n        degreesToRotate,\n        sourceWidth,\n        sourceHeight,\n        targetWidth,\n        targetHeight,\n        options);\n    calculateConfig(\n        imageReader,\n        decodeFormat,\n        isHardwareConfigAllowed,\n        isExifOrientationRequired,\n        options,\n        targetWidth,\n        targetHeight);\n\n    boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;\n    // Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.\n    if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {\n      int expectedWidth;\n      int expectedHeight;\n      if (sourceWidth >= 0\n          && sourceHeight >= 0\n          && fixBitmapToRequestedDimensions\n          && isKitKatOrGreater) {\n        expectedWidth = targetWidth;\n        expectedHeight = targetHeight;\n      } else {\n        float densityMultiplier =\n            isScaling(options) ? (float) options.inTargetDensity / options.inDensity : 1f;\n        int sampleSize = options.inSampleSize;\n        int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);\n        int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);\n        expectedWidth = Math.round(downsampledWidth * densityMultiplier);\n        expectedHeight = Math.round(downsampledHeight * densityMultiplier);\n\n        if (Log.isLoggable(TAG, Log.VERBOSE)) {\n          Log.v(\n              TAG,\n              \"Calculated target [\"\n                  + expectedWidth\n                  + \"x\"\n                  + expectedHeight\n                  + \"] for source\"\n                  + \" [\"\n                  + sourceWidth\n                  + \"x\"\n                  + sourceHeight\n                  + \"]\"\n                  + \", sampleSize: \"\n                  + sampleSize\n                  + \", targetDensity: \"\n                  + options.inTargetDensity\n                  + \", density: \"\n                  + options.inDensity\n                  + \", density multiplier: \"\n                  + densityMultiplier);\n        }\n      }\n      // If this isn't an image, or BitmapFactory was unable to parse the size, width and height\n      // will be -1 here.\n      if (expectedWidth > 0 && expectedHeight > 0) {\n        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);\n      }\n    }\n\n    if (preferredColorSpace != null) {\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n        boolean isP3Eligible =\n            preferredColorSpace == PreferredColorSpace.DISPLAY_P3\n                && options.outColorSpace != null\n                && options.outColorSpace.isWideGamut();\n        options.inPreferredColorSpace =\n            ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB);\n      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n        options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);\n      }\n    }\n\n    Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool);\n    callbacks.onDecodeComplete(bitmapPool, downsampled);\n\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      logDecode(\n          sourceWidth,\n          sourceHeight,\n          sourceMimeType,\n          options,\n          downsampled,\n          requestedWidth,\n          requestedHeight,\n          startTime);\n    }\n\n    Bitmap rotated = null;\n    if (downsampled != null) {\n      // If we scaled, the Bitmap density will be our inTargetDensity. Here we correct it back to\n      // the expected density dpi.\n      downsampled.setDensity(displayMetrics.densityDpi);\n\n      rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);\n      if (!downsampled.equals(rotated)) {\n        bitmapPool.put(downsampled);\n      }\n    }\n\n    return rotated;\n  }\n\n  private static void calculateScaling(\n      ImageType imageType,\n      ImageReader imageReader,\n      DecodeCallbacks decodeCallbacks,\n      BitmapPool bitmapPool,\n      DownsampleStrategy downsampleStrategy,\n      int degreesToRotate,\n      int sourceWidth,\n      int sourceHeight,\n      int targetWidth,\n      int targetHeight,\n      BitmapFactory.Options options)\n      throws IOException {\n    // We can't downsample source content if we can't determine its dimensions.\n    if (sourceWidth <= 0 || sourceHeight <= 0) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"Unable to determine dimensions for: \"\n                + imageType\n                + \" with target [\"\n                + targetWidth\n                + \"x\"\n                + targetHeight\n                + \"]\");\n      }\n      return;\n    }\n\n    int orientedSourceWidth = sourceWidth;\n    int orientedSourceHeight = sourceHeight;\n    // If we're rotating the image +-90 degrees, we need to downsample accordingly so the image\n    // width is decreased to near our target's height and the image height is decreased to near\n    // our target width.\n    //noinspection SuspiciousNameCombination\n    if (isRotationRequired(degreesToRotate)) {\n      orientedSourceWidth = sourceHeight;\n      orientedSourceHeight = sourceWidth;\n    }\n\n    final float exactScaleFactor =\n        downsampleStrategy.getScaleFactor(\n            orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight);\n\n    if (exactScaleFactor <= 0f) {\n      throw new IllegalArgumentException(\n          \"Cannot scale with factor: \"\n              + exactScaleFactor\n              + \" from: \"\n              + downsampleStrategy\n              + \", source: [\"\n              + sourceWidth\n              + \"x\"\n              + sourceHeight\n              + \"]\"\n              + \", target: [\"\n              + targetWidth\n              + \"x\"\n              + targetHeight\n              + \"]\");\n    }\n\n    SampleSizeRounding rounding =\n        downsampleStrategy.getSampleSizeRounding(\n            orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight);\n    if (rounding == null) {\n      throw new IllegalArgumentException(\"Cannot round with null rounding\");\n    }\n\n    int outWidth = round(exactScaleFactor * orientedSourceWidth);\n    int outHeight = round(exactScaleFactor * orientedSourceHeight);\n\n    int widthScaleFactor = orientedSourceWidth / outWidth;\n    int heightScaleFactor = orientedSourceHeight / outHeight;\n\n    // TODO: This isn't really right for both CenterOutside and CenterInside. Consider allowing\n    // DownsampleStrategy to pick, or trying to do something more sophisticated like picking the\n    // scale factor that leads to an exact match.\n    int scaleFactor =\n        rounding == SampleSizeRounding.MEMORY\n            ? Math.max(widthScaleFactor, heightScaleFactor)\n            : Math.min(widthScaleFactor, heightScaleFactor);\n\n    int powerOfTwoSampleSize;\n    // BitmapFactory does not support downsampling wbmp files on platforms <= M. See b/27305903.\n    if (Build.VERSION.SDK_INT <= 23\n        && NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {\n      powerOfTwoSampleSize = 1;\n    } else {\n      powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));\n      if (rounding == SampleSizeRounding.MEMORY\n          && powerOfTwoSampleSize < (1.f / exactScaleFactor)) {\n        powerOfTwoSampleSize = powerOfTwoSampleSize << 1;\n      }\n    }\n\n    // Here we mimic framework logic for determining how inSampleSize division is rounded on various\n    // versions of Android. The logic here has been tested on emulators for Android versions 15-26.\n    // PNG - Always uses floor\n    // JPEG - Always uses ceiling\n    // Webp - Prior to N, always uses floor. At and after N, always uses round.\n    options.inSampleSize = powerOfTwoSampleSize;\n    int powerOfTwoWidth;\n    int powerOfTwoHeight;\n    if (imageType == ImageType.JPEG) {\n      // libjpegturbo can downsample up to a sample size of 8. libjpegturbo uses ceiling to round.\n      // After libjpegturbo's native rounding, skia does a secondary scale using floor\n      // (integer division). Here we replicate that logic.\n      int nativeScaling = Math.min(powerOfTwoSampleSize, 8);\n      powerOfTwoWidth = (int) Math.ceil(orientedSourceWidth / (float) nativeScaling);\n      powerOfTwoHeight = (int) Math.ceil(orientedSourceHeight / (float) nativeScaling);\n      int secondaryScaling = powerOfTwoSampleSize / 8;\n      if (secondaryScaling > 0) {\n        powerOfTwoWidth = powerOfTwoWidth / secondaryScaling;\n        powerOfTwoHeight = powerOfTwoHeight / secondaryScaling;\n      }\n    } else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {\n      powerOfTwoWidth = (int) Math.floor(orientedSourceWidth / (float) powerOfTwoSampleSize);\n      powerOfTwoHeight = (int) Math.floor(orientedSourceHeight / (float) powerOfTwoSampleSize);\n    } else if (imageType.isWebp()) {\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n        powerOfTwoWidth = Math.round(orientedSourceWidth / (float) powerOfTwoSampleSize);\n        powerOfTwoHeight = Math.round(orientedSourceHeight / (float) powerOfTwoSampleSize);\n      } else {\n        powerOfTwoWidth = (int) Math.floor(orientedSourceWidth / (float) powerOfTwoSampleSize);\n        powerOfTwoHeight = (int) Math.floor(orientedSourceHeight / (float) powerOfTwoSampleSize);\n      }\n    } else if (orientedSourceWidth % powerOfTwoSampleSize != 0\n        || orientedSourceHeight % powerOfTwoSampleSize != 0) {\n      // If we're not confident the image is in one of our types, fall back to checking the\n      // dimensions again. inJustDecodeBounds decodes do obey inSampleSize.\n      int[] dimensions = getDimensions(imageReader, options, decodeCallbacks, bitmapPool);\n      // Power of two downsampling in BitmapFactory uses a variety of random factors to determine\n      // rounding that we can't reliably replicate for all image formats. Use ceiling here to make\n      // sure that we at least provide a Bitmap that's large enough to fit the content we're going\n      // to load.\n      powerOfTwoWidth = dimensions[0];\n      powerOfTwoHeight = dimensions[1];\n    } else {\n      powerOfTwoWidth = orientedSourceWidth / powerOfTwoSampleSize;\n      powerOfTwoHeight = orientedSourceHeight / powerOfTwoSampleSize;\n    }\n\n    double adjustedScaleFactor =\n        downsampleStrategy.getScaleFactor(\n            powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);\n\n    // Density scaling is only supported if inBitmap is null prior to KitKat. Avoid setting\n    // densities here so we calculate the final Bitmap size correctly.\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n      options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);\n      options.inDensity = getDensityMultiplier(adjustedScaleFactor);\n    }\n    if (isScaling(options)) {\n      options.inScaled = true;\n    } else {\n      options.inDensity = options.inTargetDensity = 0;\n    }\n\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(\n          TAG,\n          \"Calculate scaling\"\n              + \", source: [\"\n              + sourceWidth\n              + \"x\"\n              + sourceHeight\n              + \"]\"\n              + \", degreesToRotate: \"\n              + degreesToRotate\n              + \", target: [\"\n              + targetWidth\n              + \"x\"\n              + targetHeight\n              + \"]\"\n              + \", power of two scaled: [\"\n              + powerOfTwoWidth\n              + \"x\"\n              + powerOfTwoHeight\n              + \"]\"\n              + \", exact scale factor: \"\n              + exactScaleFactor\n              + \", power of 2 sample size: \"\n              + powerOfTwoSampleSize\n              + \", adjusted scale factor: \"\n              + adjustedScaleFactor\n              + \", target density: \"\n              + options.inTargetDensity\n              + \", density: \"\n              + options.inDensity);\n    }\n  }\n\n  /**\n   * BitmapFactory calculates the density scale factor as a float. This introduces some non-trivial\n   * error. This method attempts to account for that error by adjusting the inTargetDensity so that\n   * the final scale factor is as close to our target as possible.\n   */\n  private static int adjustTargetDensityForError(double adjustedScaleFactor) {\n    int densityMultiplier = getDensityMultiplier(adjustedScaleFactor);\n    int targetDensity = round(densityMultiplier * adjustedScaleFactor);\n    float scaleFactorWithError = targetDensity / (float) densityMultiplier;\n    double difference = adjustedScaleFactor / scaleFactorWithError;\n    return round(difference * targetDensity);\n  }\n\n  private static int getDensityMultiplier(double adjustedScaleFactor) {\n    return (int)\n        Math.round(\n            Integer.MAX_VALUE\n                * (adjustedScaleFactor <= 1D ? adjustedScaleFactor : 1 / adjustedScaleFactor));\n  }\n\n  // This is weird, but it matches the logic in a bunch of Android views/framework classes for\n  // rounding.\n  private static int round(double value) {\n    return (int) (value + 0.5d);\n  }\n\n  private boolean shouldUsePool(ImageType imageType) {\n    // On KitKat+, any bitmap (of a given config) can be used to decode any other bitmap\n    // (with the same config).\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n      return true;\n    }\n\n    // We cannot reuse bitmaps when decoding images that are not PNG or JPG prior to KitKat.\n    // See: https://groups.google.com/forum/#!msg/android-developers/Mp0MFVFi1Fo/e8ZQ9FGdWdEJ\n    return TYPES_THAT_USE_POOL_PRE_KITKAT.contains(imageType);\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  private void calculateConfig(\n      ImageReader imageReader,\n      DecodeFormat format,\n      boolean isHardwareConfigAllowed,\n      boolean isExifOrientationRequired,\n      BitmapFactory.Options optionsWithScaling,\n      int targetWidth,\n      int targetHeight) {\n\n    if (hardwareConfigState.setHardwareConfigIfAllowed(\n        targetWidth,\n        targetHeight,\n        optionsWithScaling,\n        isHardwareConfigAllowed,\n        isExifOrientationRequired)) {\n      return;\n    }\n\n    // Changing configs can cause skewing on 4.1, see issue #128.\n    if (format == DecodeFormat.PREFER_ARGB_8888\n        || Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {\n      optionsWithScaling.inPreferredConfig = Bitmap.Config.ARGB_8888;\n      return;\n    }\n\n    boolean hasAlpha = false;\n    try {\n      hasAlpha = imageReader.getImageType().hasAlpha();\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"Cannot determine whether the image has alpha or not from header\"\n                + \", format \"\n                + format,\n            e);\n      }\n    }\n\n    optionsWithScaling.inPreferredConfig =\n        hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;\n    if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {\n      optionsWithScaling.inDither = true;\n    }\n  }\n\n  /**\n   * A method for getting the dimensions of an image from the given InputStream.\n   *\n   * @param imageReader The {@link ImageReader} representing the image.\n   * @param options The options to pass to {@link BitmapFactory#decodeStream(java.io.InputStream,\n   *     android.graphics.Rect, android.graphics.BitmapFactory.Options)}.\n   * @return an array containing the dimensions of the image in the form {width, height}.\n   */\n  private static int[] getDimensions(\n      ImageReader imageReader,\n      BitmapFactory.Options options,\n      DecodeCallbacks decodeCallbacks,\n      BitmapPool bitmapPool)\n      throws IOException {\n    options.inJustDecodeBounds = true;\n    decodeStream(imageReader, options, decodeCallbacks, bitmapPool);\n    options.inJustDecodeBounds = false;\n    return new int[] {options.outWidth, options.outHeight};\n  }\n\n  private static Bitmap decodeStream(\n      ImageReader imageReader,\n      BitmapFactory.Options options,\n      DecodeCallbacks callbacks,\n      BitmapPool bitmapPool)\n      throws IOException {\n    if (!options.inJustDecodeBounds) {\n      // Once we've read the image header, we no longer need to allow the buffer to expand in\n      // size. To avoid unnecessary allocations reading image data, we fix the mark limit so that it\n      // is no larger than our current buffer size here. We need to do so immediately before\n      // decoding the full image to avoid having our mark limit overridden by other calls to\n      // mark and reset. See issue #225.\n      callbacks.onObtainBounds();\n      imageReader.stopGrowingBuffers();\n    }\n\n    // BitmapFactory.Options out* variables are reset by most calls to decodeStream, successful or\n    // otherwise, so capture here in case we log below.\n    int sourceWidth = options.outWidth;\n    int sourceHeight = options.outHeight;\n    String outMimeType = options.outMimeType;\n    final Bitmap result;\n    TransformationUtils.getBitmapDrawableLock().lock();\n    try {\n      result = imageReader.decodeBitmap(options);\n    } catch (IllegalArgumentException e) {\n      IOException bitmapAssertionException =\n          newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options);\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"Failed to decode with inBitmap, trying again without Bitmap re-use\",\n            bitmapAssertionException);\n      }\n      if (options.inBitmap != null) {\n        try {\n          bitmapPool.put(options.inBitmap);\n          options.inBitmap = null;\n          return decodeStream(imageReader, options, callbacks, bitmapPool);\n        } catch (IOException resetException) {\n          throw bitmapAssertionException;\n        }\n      }\n      throw bitmapAssertionException;\n    } finally {\n      TransformationUtils.getBitmapDrawableLock().unlock();\n    }\n\n    return result;\n  }\n\n  private static boolean isScaling(BitmapFactory.Options options) {\n    return options.inTargetDensity > 0\n        && options.inDensity > 0\n        && options.inTargetDensity != options.inDensity;\n  }\n\n  private static void logDecode(\n      int sourceWidth,\n      int sourceHeight,\n      String outMimeType,\n      BitmapFactory.Options options,\n      Bitmap result,\n      int requestedWidth,\n      int requestedHeight,\n      long startTime) {\n    Log.v(\n        TAG,\n        \"Decoded \"\n            + getBitmapString(result)\n            + \" from [\"\n            + sourceWidth\n            + \"x\"\n            + sourceHeight\n            + \"] \"\n            + outMimeType\n            + \" with inBitmap \"\n            + getInBitmapString(options)\n            + \" for [\"\n            + requestedWidth\n            + \"x\"\n            + requestedHeight\n            + \"]\"\n            + \", sample size: \"\n            + options.inSampleSize\n            + \", density: \"\n            + options.inDensity\n            + \", target density: \"\n            + options.inTargetDensity\n            + \", thread: \"\n            + Thread.currentThread().getName()\n            + \", duration: \"\n            + LogTime.getElapsedMillis(startTime));\n  }\n\n  private static String getInBitmapString(BitmapFactory.Options options) {\n    return getBitmapString(options.inBitmap);\n  }\n\n  @Nullable\n  @TargetApi(Build.VERSION_CODES.KITKAT)\n  private static String getBitmapString(Bitmap bitmap) {\n    if (bitmap == null) {\n      return null;\n    }\n\n    String sizeString =\n        Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT\n            ? \" (\" + bitmap.getAllocationByteCount() + \")\"\n            : \"\";\n    return \"[\"\n        + bitmap.getWidth()\n        + \"x\"\n        + bitmap.getHeight()\n        + \"] \"\n        + bitmap.getConfig()\n        + sizeString;\n  }\n\n  // BitmapFactory throws an IllegalArgumentException if any error occurs attempting to decode a\n  // file when inBitmap is non-null, including those caused by partial or corrupt data. We still log\n  // the error because the IllegalArgumentException is supposed to catch errors reusing Bitmaps, so\n  // want some useful log output. In most cases this can be safely treated as a normal IOException.\n  private static IOException newIoExceptionForInBitmapAssertion(\n      IllegalArgumentException e,\n      int outWidth,\n      int outHeight,\n      String outMimeType,\n      BitmapFactory.Options options) {\n    return new IOException(\n        \"Exception decoding bitmap\"\n            + \", outWidth: \"\n            + outWidth\n            + \", outHeight: \"\n            + outHeight\n            + \", outMimeType: \"\n            + outMimeType\n            + \", inBitmap: \"\n            + getInBitmapString(options),\n        e);\n  }\n\n  @SuppressWarnings(\"PMD.CollapsibleIfStatements\")\n  @TargetApi(Build.VERSION_CODES.O)\n  private static void setInBitmap(\n      BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {\n    @Nullable Bitmap.Config expectedConfig = null;\n    // Avoid short circuiting, it appears to break on some devices.\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n      if (options.inPreferredConfig == Config.HARDWARE) {\n        return;\n      }\n      // On API 26 outConfig may be null for some images even if the image is valid, can be decoded\n      // and outWidth/outHeight/outColorSpace are populated (see b/71513049).\n      expectedConfig = options.outConfig;\n    }\n\n    if (expectedConfig == null) {\n      // We're going to guess that BitmapFactory will return us the config we're requesting. This\n      // isn't always the case, even though our guesses tend to be conservative and prefer configs\n      // of larger sizes so that the Bitmap will fit our image anyway. If we're wrong here and the\n      // config we choose is too small, our initial decode will fail, but we will retry with no\n      // inBitmap which will succeed so if we're wrong here, we're less efficient but still correct.\n      expectedConfig = options.inPreferredConfig;\n    }\n    // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.\n    options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);\n  }\n\n  private static synchronized BitmapFactory.Options getDefaultOptions() {\n    BitmapFactory.Options decodeBitmapOptions;\n    synchronized (OPTIONS_QUEUE) {\n      decodeBitmapOptions = OPTIONS_QUEUE.poll();\n    }\n    if (decodeBitmapOptions == null) {\n      decodeBitmapOptions = new BitmapFactory.Options();\n      resetOptions(decodeBitmapOptions);\n    }\n\n    return decodeBitmapOptions;\n  }\n\n  private static void releaseOptions(BitmapFactory.Options decodeBitmapOptions) {\n    resetOptions(decodeBitmapOptions);\n    synchronized (OPTIONS_QUEUE) {\n      OPTIONS_QUEUE.offer(decodeBitmapOptions);\n    }\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {\n    decodeBitmapOptions.inTempStorage = null;\n    decodeBitmapOptions.inDither = false;\n    decodeBitmapOptions.inScaled = false;\n    decodeBitmapOptions.inSampleSize = 1;\n    decodeBitmapOptions.inPreferredConfig = null;\n    decodeBitmapOptions.inJustDecodeBounds = false;\n    decodeBitmapOptions.inDensity = 0;\n    decodeBitmapOptions.inTargetDensity = 0;\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n      decodeBitmapOptions.inPreferredColorSpace = null;\n      decodeBitmapOptions.outColorSpace = null;\n      decodeBitmapOptions.outConfig = null;\n    }\n    decodeBitmapOptions.outWidth = 0;\n    decodeBitmapOptions.outHeight = 0;\n    decodeBitmapOptions.outMimeType = null;\n    decodeBitmapOptions.inBitmap = null;\n    decodeBitmapOptions.inMutable = true;\n  }\n\n  /** Callbacks for key points during decodes. */\n  public interface DecodeCallbacks {\n    void onObtainBounds();\n\n    void onDecodeComplete(BitmapPool bitmapPool, Bitmap downsampled) throws IOException;\n  }\n\n  private static boolean isRotationRequired(int degreesToRotate) {\n    return degreesToRotate == 90 || degreesToRotate == 270;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/DrawableToBitmapConverter.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.drawable.Animatable;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.request.target.Target;\nimport java.util.concurrent.locks.Lock;\n\nfinal class DrawableToBitmapConverter {\n  private static final String TAG = \"DrawableToBitmap\";\n  private static final BitmapPool NO_RECYCLE_BITMAP_POOL =\n      new BitmapPoolAdapter() {\n        @Override\n        public void put(Bitmap bitmap) {\n          // Avoid calling super to avoid recycling the given Bitmap.\n        }\n      };\n\n  private DrawableToBitmapConverter() {\n    // Utility class.\n  }\n\n  @Nullable\n  static Resource<Bitmap> convert(BitmapPool bitmapPool, Drawable drawable, int width, int height) {\n    // Handle DrawableContainer or StateListDrawables that may contain one or more BitmapDrawables.\n    drawable = drawable.getCurrent();\n    Bitmap result = null;\n    boolean isRecycleable = false;\n    if (drawable instanceof BitmapDrawable) {\n      result = ((BitmapDrawable) drawable).getBitmap();\n    } else if (!(drawable instanceof Animatable)) {\n      result = drawToBitmap(bitmapPool, drawable, width, height);\n      // We created and drew to the Bitmap, so it's safe for us to recycle or re-use.\n      isRecycleable = true;\n    }\n\n    BitmapPool toUse = isRecycleable ? bitmapPool : NO_RECYCLE_BITMAP_POOL;\n    return BitmapResource.obtain(result, toUse);\n  }\n\n  @Nullable\n  private static Bitmap drawToBitmap(\n      BitmapPool bitmapPool, Drawable drawable, int width, int height) {\n    if (width == Target.SIZE_ORIGINAL && drawable.getIntrinsicWidth() <= 0) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(\n            TAG,\n            \"Unable to draw \"\n                + drawable\n                + \" to Bitmap with Target.SIZE_ORIGINAL because the\"\n                + \" Drawable has no intrinsic width\");\n      }\n      return null;\n    }\n    if (height == Target.SIZE_ORIGINAL && drawable.getIntrinsicHeight() <= 0) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(\n            TAG,\n            \"Unable to draw \"\n                + drawable\n                + \" to Bitmap with Target.SIZE_ORIGINAL because the\"\n                + \" Drawable has no intrinsic height\");\n      }\n      return null;\n    }\n    int targetWidth = drawable.getIntrinsicWidth() > 0 ? drawable.getIntrinsicWidth() : width;\n    int targetHeight = drawable.getIntrinsicHeight() > 0 ? drawable.getIntrinsicHeight() : height;\n\n    Lock lock = TransformationUtils.getBitmapDrawableLock();\n    lock.lock();\n    Bitmap result = bitmapPool.get(targetWidth, targetHeight, Bitmap.Config.ARGB_8888);\n    try {\n      Canvas canvas = new Canvas(result);\n      drawable.setBounds(0, 0, targetWidth, targetHeight);\n      drawable.draw(canvas);\n      canvas.setBitmap(null);\n    } finally {\n      lock.unlock();\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/DrawableTransformation.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport java.security.MessageDigest;\n\n/**\n * Applies a {@link Bitmap} {@link Transformation} to {@link Drawable}s by first attempting to\n * convert the {@link Drawable} to a {@link Bitmap} and then running the {@link Transformation} on\n * the converted {@link Bitmap}.\n *\n * <p>This class is relatively efficient for {@link BitmapDrawable} where the {@link Bitmap} is\n * readily accessible. For non-{@link Bitmap} based {@link Drawable}s, this class must first try to\n * draw the {@link Drawable} to a {@link Bitmap} using {@link android.graphics.Canvas}, which is\n * less efficient. {@link Drawable}s that implement {@link android.graphics.drawable.Animatable}\n * will fail with an exception. {@link Drawable}s that return {@code <= 0} for {@link\n * Drawable#getIntrinsicHeight()} and/or {@link Drawable#getIntrinsicWidth()} will fail with an\n * exception if the requested size is {@link\n * com.bumptech.glide.request.target.Target#SIZE_ORIGINAL}. {@link Drawable}s without intrinsic\n * dimensions are drawn using the dimensions provided in {@link Transformation#transform(Context,\n * Resource, int, int)}. As a result, they may be transformed incorrectly or in unexpected ways.\n */\npublic class DrawableTransformation implements Transformation<Drawable> {\n\n  private final Transformation<Bitmap> wrapped;\n  private final boolean isRequired;\n\n  public DrawableTransformation(Transformation<Bitmap> wrapped, boolean isRequired) {\n    this.wrapped = wrapped;\n    this.isRequired = isRequired;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public Transformation<BitmapDrawable> asBitmapDrawable() {\n    return (Transformation<BitmapDrawable>) (Transformation<?>) this;\n  }\n\n  @NonNull\n  @Override\n  public Resource<Drawable> transform(\n      @NonNull Context context, @NonNull Resource<Drawable> resource, int outWidth, int outHeight) {\n    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();\n    Drawable drawable = resource.get();\n    Resource<Bitmap> bitmapResourceToTransform =\n        DrawableToBitmapConverter.convert(bitmapPool, drawable, outWidth, outHeight);\n    if (bitmapResourceToTransform == null) {\n      if (isRequired) {\n        throw new IllegalArgumentException(\"Unable to convert \" + drawable + \" to a Bitmap\");\n      } else {\n        return resource;\n      }\n    }\n    Resource<Bitmap> transformedBitmapResource =\n        wrapped.transform(context, bitmapResourceToTransform, outWidth, outHeight);\n\n    if (transformedBitmapResource.equals(bitmapResourceToTransform)) {\n      transformedBitmapResource.recycle();\n      return resource;\n    } else {\n      return newDrawableResource(context, transformedBitmapResource);\n    }\n  }\n\n  // It's clearer to cast the result in a separate line from obtaining it.\n  @SuppressWarnings({\"unchecked\", \"PMD.UnnecessaryLocalBeforeReturn\"})\n  private Resource<Drawable> newDrawableResource(Context context, Resource<Bitmap> transformed) {\n    Resource<? extends Drawable> result =\n        LazyBitmapDrawableResource.obtain(context.getResources(), transformed);\n    return (Resource<Drawable>) result;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof DrawableTransformation) {\n      DrawableTransformation other = (DrawableTransformation) o;\n      return wrapped.equals(other.wrapped);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return wrapped.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    wrapped.updateDiskCacheKey(messageDigest);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/ExifInterfaceImageHeaderParser.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.os.Build;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.exifinterface.media.ExifInterface;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\n\n/**\n * Uses {@link ExifInterface} to parse orientation data.\n *\n * <p>ExifInterface supports the HEIF format on OMR1+. Glide's {@link DefaultImageHeaderParser}\n * doesn't currently support HEIF. In the future we should reconcile these two classes, but for now\n * this is a simple way to ensure that HEIF files are oriented correctly on platforms where they're\n * supported.\n */\n@RequiresApi(Build.VERSION_CODES.O_MR1)\npublic final class ExifInterfaceImageHeaderParser implements ImageHeaderParser {\n\n  @NonNull\n  @Override\n  public ImageType getType(@NonNull InputStream is) {\n    return ImageType.UNKNOWN;\n  }\n\n  @NonNull\n  @Override\n  public ImageType getType(@NonNull ByteBuffer byteBuffer) {\n    return ImageType.UNKNOWN;\n  }\n\n  @Override\n  public int getOrientation(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    ExifInterface exifInterface = new ExifInterface(is);\n    int result =\n        exifInterface.getAttributeInt(\n            ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);\n    if (result == ExifInterface.ORIENTATION_UNDEFINED) {\n      return ImageHeaderParser.UNKNOWN_ORIENTATION;\n    }\n    return result;\n  }\n\n  @Override\n  public int getOrientation(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    return getOrientation(ByteBufferUtil.toStream(byteBuffer), byteArrayPool);\n  }\n\n  @Override\n  public boolean hasJpegMpf(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    return false;\n  }\n\n  @Override\n  public boolean hasJpegMpf(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)\n      throws IOException {\n    return false;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/FitCenter.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport java.security.MessageDigest;\n\n/**\n * Scales the image uniformly (maintaining the image's aspect ratio) so that one of the dimensions\n * of the image will be equal to the given dimension and the other will be less than the given\n * dimension.\n */\npublic class FitCenter extends BitmapTransformation {\n  private static final String ID = \"com.bumptech.glide.load.resource.bitmap.FitCenter\";\n  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);\n\n  @Override\n  protected Bitmap transform(\n      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n    return TransformationUtils.fitCenter(pool, toTransform, outWidth, outHeight);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof FitCenter;\n  }\n\n  @Override\n  public int hashCode() {\n    return ID.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(ID_BYTES);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/GlideBitmapFactory.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\nimport android.graphics.BitmapFactory;\nimport android.graphics.BitmapFactory.Options;\nimport android.graphics.Canvas;\nimport android.graphics.ColorMatrixColorFilter;\nimport android.graphics.Gainmap;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.os.Build.VERSION;\nimport android.os.Build.VERSION_CODES;\nimport android.util.Log;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.util.GlideSuppliers;\nimport com.bumptech.glide.util.GlideSuppliers.GlideSupplier;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.FileDescriptor;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Wrapper around {@link BitmapFactory} to work around known issues with {@link BitmapFactory}\n * across Android SDK levels.\n *\n * <p>In particular, this class works around these known issues:\n *\n * <ul>\n *   <li>Ultra HDR image single-channel gainmaps not being decoded on Android U when hardware\n *       bitmaps are enabled. This issue is further described in\n *       https://github.com/bumptech/glide/issues/5362.\n * </ul>\n *\n * <p>New usages of {@link BitmapFactory} APIs within Glide should be added here rather than called\n * directly.\n */\nfinal class GlideBitmapFactory {\n\n  private static final String TAG = \"GlideBitmapFactory\";\n\n  private GlideBitmapFactory() {}\n\n  /** Wrapper for {@link BitmapFactory#decodeStream}. */\n  @Nullable\n  public static Bitmap decodeStream(\n      InputStream inputStream, BitmapFactory.Options options, ImageReader reader) {\n    if (VERSION.SDK_INT == VERSION_CODES.UPSIDE_DOWN_CAKE\n        && GainmapDecoderWorkaroundStateCalculator.needsGainmapDecodeWorkaround(options)\n        && isLikelyToContainGainmap(reader)) {\n      return safeAndExpensiveDecodeHardwareBitmapWithGainmap(inputStream, options);\n    }\n    return BitmapFactory.decodeStream(inputStream, /* outPadding= */ null, options);\n  }\n\n  /** Wrapper for {@link BitmapFactory#decodeByteArray}. */\n  @Nullable\n  public static Bitmap decodeByteArray(\n      byte[] bytes, BitmapFactory.Options options, ImageReader reader) {\n    if (VERSION.SDK_INT == VERSION_CODES.UPSIDE_DOWN_CAKE\n        && GainmapDecoderWorkaroundStateCalculator.needsGainmapDecodeWorkaround(options)\n        && isLikelyToContainGainmap(reader)) {\n      return safeAndExpensiveDecodeHardwareBitmapWithGainmap(bytes, options);\n    }\n    return BitmapFactory.decodeByteArray(bytes, /* offset= */ 0, bytes.length, options);\n  }\n\n  /** Wrapper for {@link BitmapFactory#decodeFileDescriptor}. */\n  @Nullable\n  public static Bitmap decodeFileDescriptor(\n      FileDescriptor fileDescriptor, BitmapFactory.Options options, ImageReader reader) {\n    if (VERSION.SDK_INT == VERSION_CODES.UPSIDE_DOWN_CAKE\n        && GainmapDecoderWorkaroundStateCalculator.needsGainmapDecodeWorkaround(options)\n        && isLikelyToContainGainmap(reader)) {\n      return safeAndExpensiveDecodeHardwareBitmapWithGainmap(fileDescriptor, options);\n    }\n    return BitmapFactory.decodeFileDescriptor(fileDescriptor, /* outPadding= */ null, options);\n  }\n\n  /**\n   * Returns whether the image referenced by the {@link ImageReader} is likely to have a gainmap.\n   *\n   * <p>On Android devices, a JPEG with multi-picture format (MPF) metadata is very likely to\n   * contain a gainmap, either it being an Ultra HDR JPEG or a ISO 21496-1 JPEG.\n   */\n  private static boolean isLikelyToContainGainmap(ImageReader imageReader) {\n    try {\n      boolean hasMpf = imageReader.hasJpegMpf();\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"isLikelyToContainGainmap=\" + hasMpf);\n      }\n      return hasMpf;\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"isLikelyToContainGainmap failed\", e);\n      }\n    }\n    return false;\n  }\n\n  /**\n   * Returns a decoded bitmap for the input stream, ensuring that any associated gainmap is decoded\n   * without being silently dropped on Android U.\n   *\n   * <p>If the input stream does not reference an image with a gainmap, then this method simply\n   * returns a hardware bitmap.\n   *\n   * <p>This method safely wraps BitmapFactory#decodeStream(InputStream, Rect, Options)} on Android\n   * U.\n   *\n   * <p>This method performs an expensive workaround, using software bitmap decoding. It is\n   * recommended to only use this check on images that have a reasonable chance of containing\n   * gainmaps (e.g., they already contain JPEG multi-picture format metadata).\n   *\n   * @param inputStream for the bitmap to be decoded.\n   * @param options to be applied in the {@link BitmapFactory#decodeStream} call.\n   */\n  @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)\n  @Nullable\n  private static Bitmap safeAndExpensiveDecodeHardwareBitmapWithGainmap(\n      InputStream inputStream, Options options) {\n    Preconditions.checkArgument(options.inPreferredConfig == Config.HARDWARE);\n    Bitmap softwareBitmap = null;\n    options.inPreferredConfig = Config.ARGB_8888;\n    try {\n      softwareBitmap = BitmapFactory.decodeStream(inputStream, /* outPadding= */ null, options);\n      if (softwareBitmap == null) {\n        return null;\n      }\n      return safeDecodeBitmapWithGainmap(softwareBitmap);\n    } finally {\n      if (softwareBitmap != null) {\n        softwareBitmap.recycle();\n      }\n      options.inPreferredConfig = Config.HARDWARE;\n    }\n  }\n\n  /**\n   * Returns a decoded bitmap for the input byte array, ensuring that any associated gainmap is\n   * decoded without being silently dropped on Android U.\n   *\n   * <p>If the input bytes do not reference an image with a gainmap, then this method simply returns\n   * a hardware bitmap.\n   *\n   * <p>This method safely wraps BitmapFactory#decodeByteArray(byte[], int, int)} on Android U.\n   *\n   * @param bytes for the bitmap to be decoded.\n   * @param options to be applied in the {@link BitmapFactory#decodeByteArray} call. This must be\n   *     set to {@link Config#HARDWARE}.\n   * @throws IllegalArgumentException if {@link Options#inPreferredConfig} is set to any state other\n   *     than {@link Config#HARDWARE}.\n   */\n  @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)\n  @Nullable\n  private static Bitmap safeAndExpensiveDecodeHardwareBitmapWithGainmap(\n      byte[] bytes, Options options) {\n    Preconditions.checkArgument(options.inPreferredConfig == Config.HARDWARE);\n    options.inPreferredConfig = Bitmap.Config.ARGB_8888;\n    Bitmap softwareBitmap = null;\n    try {\n      softwareBitmap = BitmapFactory.decodeByteArray(bytes, /* offset= */ 0, bytes.length, options);\n      if (softwareBitmap == null) {\n        return null;\n      }\n      return GlideBitmapFactory.safeDecodeBitmapWithGainmap(softwareBitmap);\n    } finally {\n      if (softwareBitmap != null) {\n        softwareBitmap.recycle();\n      }\n      options.inPreferredConfig = Config.HARDWARE;\n    }\n  }\n\n  /**\n   * Returns a decoded bitmap for the input file descriptor, ensuring that any associated gainmap is\n   * decoded without being silently dropped on Android U.\n   *\n   * <p>If the input file descriptor does not reference an image with a gainmap, then this method\n   * simply returns a hardware bitmap.\n   *\n   * <p>This method safely wraps {@link BitmapFactory#decodeFileDescriptor(FileDescriptor, Rect,\n   * Options)} on Android U.\n   *\n   * @param fileDescriptor from which the bitmap will be decoded.\n   * @param options to be applied in the {@link BitmapFactory#decodeFileDescriptor} call. This must\n   *     be set to {@link Config#HARDWARE}.\n   * @throws IllegalArgumentException if {@link Options#inPreferredConfig} is set to any state other\n   *     than {@link Config#HARDWARE}.\n   */\n  @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)\n  @Nullable\n  private static Bitmap safeAndExpensiveDecodeHardwareBitmapWithGainmap(\n      FileDescriptor fileDescriptor, Options options) {\n    Preconditions.checkArgument(options.inPreferredConfig == Config.HARDWARE);\n    Bitmap softwareBitmap = null;\n    options.inPreferredConfig = Bitmap.Config.ARGB_8888;\n    try {\n      softwareBitmap =\n          BitmapFactory.decodeFileDescriptor(fileDescriptor, /* outPadding= */ null, options);\n      if (softwareBitmap == null) {\n        return null;\n      }\n      return GlideBitmapFactory.safeDecodeBitmapWithGainmap(softwareBitmap);\n    } finally {\n      if (softwareBitmap != null) {\n        softwareBitmap.recycle();\n      }\n      options.inPreferredConfig = Config.HARDWARE;\n    }\n  }\n\n  /**\n   * Returns a decoded bitmap for the input software bitmap, ensuring that any associated gainmap is\n   * decoded without errors on Android U if it is a valid gainmap.\n   *\n   * @param softwareBitmap The bitmap to be decoded. Must not be a hardware bitmap. The caller of\n   *     this method is responsible for recycling this bitmap.\n   * @throws IllegalArgumentException if {@link Options#inPreferredConfig} is set to any state other\n   *     than {@link Config#HARDWARE}.\n   */\n  @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)\n  @Nullable\n  private static Bitmap safeDecodeBitmapWithGainmap(Bitmap softwareBitmap) {\n    Gainmap gainmap = softwareBitmap.getGainmap();\n    if (gainmap != null) {\n      Bitmap gainmapContents = gainmap.getGainmapContents();\n      if (gainmapContents.getConfig() == Config.ALPHA_8) {\n        softwareBitmap.setGainmap(\n            GainmapCopier.convertSingleChannelGainmapToTripleChannelGainmap(gainmap));\n      }\n    }\n    return softwareBitmap.copy(Config.HARDWARE, /* isMutable= */ false);\n  }\n\n  /** Utils to copy gainmaps. */\n  @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)\n  private static final class GainmapCopier {\n\n    /** Transforms a bitmap so that the output alpha is opaque. */\n    private static final ColorMatrixColorFilter OPAQUE_FILTER =\n        new ColorMatrixColorFilter(\n            new float[] {\n              0f, 0f, 0f, 1f, 0f,\n              0f, 0f, 0f, 1f, 0f,\n              0f, 0f, 0f, 1f, 0f,\n              0f, 0f, 0f, 0f, 255f\n            });\n\n    private GainmapCopier() {}\n\n    /**\n     * Converts single channel gainmap to triple channel, where a single channel gainmap is defined\n     * as a gainmap with a bitmap config of {@link Config#ALPHA_8}.\n     *\n     * <p>If the input gainmap is not single channel or the copy operation fails, then this method\n     * will just return the original gainmap.\n     */\n    public static Gainmap convertSingleChannelGainmapToTripleChannelGainmap(Gainmap gainmap) {\n      Bitmap gainmapContents = gainmap.getGainmapContents();\n      if (gainmapContents.getConfig() != Config.ALPHA_8) {\n        return gainmap;\n      }\n      Bitmap newContents = copyAlpha8ToOpaqueArgb888(gainmapContents);\n      Gainmap newGainmap = new Gainmap(newContents);\n      float[] tempFloatArray = gainmap.getRatioMin();\n      newGainmap.setRatioMin(tempFloatArray[0], tempFloatArray[1], tempFloatArray[2]);\n      tempFloatArray = gainmap.getRatioMax();\n      newGainmap.setRatioMax(tempFloatArray[0], tempFloatArray[1], tempFloatArray[2]);\n      tempFloatArray = gainmap.getGamma();\n      newGainmap.setGamma(tempFloatArray[0], tempFloatArray[1], tempFloatArray[2]);\n      tempFloatArray = gainmap.getEpsilonSdr();\n      newGainmap.setEpsilonSdr(tempFloatArray[0], tempFloatArray[1], tempFloatArray[2]);\n      tempFloatArray = gainmap.getEpsilonHdr();\n      newGainmap.setEpsilonHdr(tempFloatArray[0], tempFloatArray[1], tempFloatArray[2]);\n      newGainmap.setDisplayRatioForFullHdr(gainmap.getDisplayRatioForFullHdr());\n      newGainmap.setMinDisplayRatioForHdrTransition(gainmap.getMinDisplayRatioForHdrTransition());\n      return newGainmap;\n    }\n\n    /**\n     * Converts an {@link Config#ALPHA_8} bitmap to a {@link Config#ARGB_8888} bitmap with the alpha\n     * channel set to unity so that the output bitmap is opaque.\n     *\n     * @throws IllegalArgumentException if called with a bitmap with a config that is not {@link\n     *     Config#ALPHA_8}\n     */\n    private static Bitmap copyAlpha8ToOpaqueArgb888(Bitmap bitmap) {\n      Preconditions.checkArgument(bitmap.getConfig() == Config.ALPHA_8);\n      // We have to use a canvas operation with an opaque alpha filter to draw the gainmap. We can't\n      // use bitmap.copy(Config.ARGB_8888, /* isMutable= */ false) because copying from A8 to RBGA\n      // will result in zero-valued RGB values.\n      Bitmap newContents =\n          Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);\n      Canvas canvas = new Canvas(newContents);\n      Paint paint = new Paint();\n      paint.setColorFilter(OPAQUE_FILTER);\n      canvas.drawBitmap(bitmap, /* left= */ 0f, /* top= */ 0f, paint);\n      canvas.setBitmap(null);\n      return newContents;\n    }\n  }\n\n  /**\n   * Determines if a gainmap decoding workaround is required to mitigate an Android U bug with\n   * decoding bitmaps with gainmaps. When the following conditions are present, Android U will not\n   * be able to decode a gainmap, with hardware bitmap operation failing for the gainmap:\n   *\n   * <ul>\n   *   <li>The HWUI is configured to use skiagl.\n   *   <li>The gainmap is single channel.\n   *   <li>The bitmap owning the bitmap is a hardware bitmap.\n   * </ul>\n   *\n   * <p>Callers should use this class to determine whether to apply a workaround, e.g., modifying\n   * the gainmap to be triple channel and software decode it.\n   */\n  public static final class GainmapDecoderWorkaroundStateCalculator {\n    private static final String TAG = \"GainmapWorkaroundCalc\";\n\n    /** Meomizes result of test to see if the device is susceptible to the gainmap decoding bug. */\n    private static final GlideSupplier<Boolean> REQUIRES_GAIN_MAP_FIX =\n        GlideSuppliers.memorize(() -> calculateNeedsGainmapDecodeWorkaround());\n\n    private GainmapDecoderWorkaroundStateCalculator() {}\n\n    /**\n     * Returns true if a gainmap decoding workaround is required to mitigate an Android U bug. This\n     * method tests for the presence of the bug, which only affects hardware bitmaps, and caches the\n     * result in memory.\n     *\n     * <p>This method is thread-safe.\n     *\n     * @param options which will be used to decode the gainmap.\n     */\n    private static boolean needsGainmapDecodeWorkaround(Options options) {\n      if (VERSION.SDK_INT != VERSION_CODES.UPSIDE_DOWN_CAKE) {\n        return false;\n      }\n      if (options.inPreferredConfig != Config.HARDWARE) {\n        return false;\n      }\n      return REQUIRES_GAIN_MAP_FIX.get();\n    }\n\n    private static boolean calculateNeedsGainmapDecodeWorkaround() {\n      if (VERSION.SDK_INT != VERSION_CODES.UPSIDE_DOWN_CAKE) {\n        return false;\n      }\n      // Create a 1x1 single channel, A8 bitmap and attempt to copy to a hardware bitmap. If the\n      // copy operation fails, then the device requires a workaround to decode hardware\n      // gainmaps.\n      Bitmap a8Source = Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Config.ALPHA_8);\n      Bitmap a8HardwareBitmap = a8Source.copy(Config.HARDWARE, /* isMutable= */ false);\n      a8Source.recycle();\n      boolean needsGainmapDecodeWorkaround = a8HardwareBitmap == null;\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"calculateNeedsGainmapDecodeWorkaround=\" + needsGainmapDecodeWorkaround);\n      }\n      if (a8HardwareBitmap != null) {\n        a8HardwareBitmap.recycle();\n      }\n      return needsGainmapDecodeWorkaround;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/GranularRoundedCorners.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Util;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\n\n/** A {@link BitmapTransformation} which has a different radius for each corner of a bitmap. */\npublic final class GranularRoundedCorners extends BitmapTransformation {\n  private static final String ID = \"com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners\";\n  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);\n\n  private final float topLeft;\n  private final float topRight;\n  private final float bottomRight;\n  private final float bottomLeft;\n\n  /** Provide the radii to round the corners of the bitmap. */\n  public GranularRoundedCorners(\n      float topLeft, float topRight, float bottomRight, float bottomLeft) {\n    this.topLeft = topLeft;\n    this.topRight = topRight;\n    this.bottomRight = bottomRight;\n    this.bottomLeft = bottomLeft;\n  }\n\n  @Override\n  protected Bitmap transform(\n      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n    return TransformationUtils.roundedCorners(\n        pool, toTransform, topLeft, topRight, bottomRight, bottomLeft);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof GranularRoundedCorners) {\n      GranularRoundedCorners other = (GranularRoundedCorners) o;\n      return topLeft == other.topLeft\n          && topRight == other.topRight\n          && bottomRight == other.bottomRight\n          && bottomLeft == other.bottomLeft;\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    int hashCode = Util.hashCode(ID.hashCode(), Util.hashCode(topLeft));\n    hashCode = Util.hashCode(topRight, hashCode);\n    hashCode = Util.hashCode(bottomRight, hashCode);\n    return Util.hashCode(bottomLeft, hashCode);\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(ID_BYTES);\n\n    byte[] radiusData =\n        ByteBuffer.allocate(16)\n            .putFloat(topLeft)\n            .putFloat(topRight)\n            .putFloat(bottomRight)\n            .putFloat(bottomLeft)\n            .array();\n    messageDigest.update(radiusData);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/HardwareConfigState.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.annotation.TargetApi;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.os.Build;\nimport android.os.Build.VERSION_CODES;\nimport android.util.Log;\nimport androidx.annotation.ChecksSdkIntAtLeast;\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.util.Util;\nimport java.io.File;\nimport java.util.Arrays;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * State and constants for interacting with {@link android.graphics.Bitmap.Config#HARDWARE} on\n * Android O+.\n */\npublic final class HardwareConfigState {\n  private static final String TAG = \"HardwareConfig\";\n\n  /**\n   * Force the state to wait until a call to allow hardware Bitmaps to be used when they'd otherwise\n   * be eligible to work around a framework issue pre Q that can cause a native crash when\n   * allocating a hardware Bitmap in this specific circumstance. See b/126573603#comment12 for\n   * details.\n   */\n  public static final boolean BLOCK_HARDWARE_BITMAPS_WHEN_GL_CONTEXT_MIGHT_NOT_BE_INITIALIZED =\n      Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;\n\n  /** Support for the hardware bitmap config was added in Android O. */\n  @ChecksSdkIntAtLeast(api = VERSION_CODES.P)\n  public static final boolean HARDWARE_BITMAPS_SUPPORTED =\n      Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;\n\n  /**\n   * Allows us to check to make sure we're not exceeding the FD limit for a process with hardware\n   * {@link Bitmap}s.\n   *\n   * <p>{@link Bitmap.Config#HARDWARE} {@link Bitmap}s require two FDs (depending on the driver).\n   * Processes have an FD limit of 1024 (at least on O). With sufficiently small {@link Bitmap}s\n   * and/or a sufficiently large {@link com.bumptech.glide.load.engine.cache.MemoryCache}, we can\n   * end up with enough {@link Bitmap}s in memory that we blow through the FD limit, which causes\n   * graphics errors, Binder errors, and a variety of crashes.\n   *\n   * <p>Calling list.size() should be relatively efficient (hopefully < 1ms on average) because\n   * /proc is an in-memory FS.\n   */\n  private static final File FD_SIZE_LIST = new File(\"/proc/self/fd\");\n\n  /**\n   * Each FD check takes 1-2ms, so to avoid overhead, only check every N decodes. 50 is more or less\n   * arbitrary.\n   */\n  private static final int MINIMUM_DECODES_BETWEEN_FD_CHECKS = 50;\n\n  // 20k.\n  private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P = 20000;\n\n  /**\n   * Some P devices seem to have a more O like FD count, so we'll manually reduce the number of FDs\n   * we use for hardware bitmaps. See b/139097735.\n   */\n  private static final int REDUCED_MAX_FDS_FOR_HARDWARE_CONFIGS_P = 500;\n\n  /**\n   * @deprecated This constant is unused and will be removed in a future version, avoid using it.\n   */\n  @Deprecated public static final int NO_MAX_FD_COUNT = -1;\n\n  private static volatile HardwareConfigState instance;\n\n  private final int sdkBasedMaxFdCount;\n\n  @GuardedBy(\"this\")\n  private int decodesSinceLastFdCheck;\n\n  @GuardedBy(\"this\")\n  private boolean isFdSizeBelowHardwareLimit = true;\n\n  /**\n   * Only mutated on the main thread. Read by any number of background threads concurrently.\n   *\n   * <p>Defaults to {@code false} because we need to wait for the GL context to be initialized and\n   * it defaults to not initialized (https://b.corp.google.com/issues/126573603#comment12).\n   */\n  private final AtomicBoolean isHardwareConfigAllowedByAppState = new AtomicBoolean(false);\n\n  public static HardwareConfigState getInstance() {\n    if (instance == null) {\n      synchronized (HardwareConfigState.class) {\n        if (instance == null) {\n          instance = new HardwareConfigState();\n        }\n      }\n    }\n    return instance;\n  }\n\n  @VisibleForTesting\n  HardwareConfigState() {\n    sdkBasedMaxFdCount = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P;\n  }\n\n  public void blockHardwareBitmaps() {\n    Util.assertMainThread();\n    isHardwareConfigAllowedByAppState.set(false);\n  }\n\n  public void unblockHardwareBitmaps() {\n    Util.assertMainThread();\n    isHardwareConfigAllowedByAppState.set(true);\n  }\n\n  public boolean isHardwareConfigAllowed(\n      int targetWidth,\n      int targetHeight,\n      boolean isHardwareConfigAllowed,\n      boolean isExifOrientationRequired) {\n    if (!isHardwareConfigAllowed) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Hardware config disallowed by caller\");\n      }\n      return false;\n    }\n    if (!HARDWARE_BITMAPS_SUPPORTED) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Hardware config disallowed by sdk\");\n      }\n      return false;\n    }\n    if (areHardwareBitmapsBlockedByAppState()) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Hardware config disallowed by app state\");\n      }\n      return false;\n    }\n    if (isExifOrientationRequired) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Hardware config disallowed because exif orientation is required\");\n      }\n      return false;\n    }\n    if (targetWidth < 0 || targetHeight < 0) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Hardware config disallowed because of invalid dimensions\");\n      }\n      return false;\n    }\n    // Make sure to call isFdSizeBelowHardwareLimit last because it has side affects.\n    if (!isFdSizeBelowHardwareLimit()) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Hardware config disallowed because there are insufficient FDs\");\n      }\n      return false;\n    }\n\n    return true;\n  }\n\n  private boolean areHardwareBitmapsBlockedByAppState() {\n    return BLOCK_HARDWARE_BITMAPS_WHEN_GL_CONTEXT_MIGHT_NOT_BE_INITIALIZED\n        && !isHardwareConfigAllowedByAppState.get();\n  }\n\n  @TargetApi(Build.VERSION_CODES.O)\n  boolean setHardwareConfigIfAllowed(\n      int targetWidth,\n      int targetHeight,\n      BitmapFactory.Options optionsWithScaling,\n      boolean isHardwareConfigAllowed,\n      boolean isExifOrientationRequired) {\n    boolean result =\n        isHardwareConfigAllowed(\n            targetWidth, targetHeight, isHardwareConfigAllowed, isExifOrientationRequired);\n    if (result) {\n      optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;\n      optionsWithScaling.inMutable = false;\n    }\n    return result;\n  }\n\n  private static boolean isHardwareBitmapCountReducedOnApi28ByB139097735() {\n    if (Build.VERSION.SDK_INT != Build.VERSION_CODES.P) {\n      return false;\n    }\n    for (String prefixOrModelName :\n        Arrays.asList(\n            \"GM1900\",\n            \"GM1901\",\n            \"GM1903\",\n            \"GM1911\",\n            \"GM1915\",\n            \"ONEPLUS A3000\",\n            \"ONEPLUS A3010\",\n            \"ONEPLUS A5010\",\n            \"ONEPLUS A5000\",\n            \"ONEPLUS A3003\",\n            \"ONEPLUS A6000\",\n            \"ONEPLUS A6003\",\n            \"ONEPLUS A6010\",\n            \"ONEPLUS A6013\")) {\n      if (Build.MODEL.startsWith(prefixOrModelName)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private int getMaxFdCount() {\n    if (isHardwareBitmapCountReducedOnApi28ByB139097735()) {\n      return REDUCED_MAX_FDS_FOR_HARDWARE_CONFIGS_P;\n    }\n    return sdkBasedMaxFdCount;\n  }\n\n  private synchronized boolean isFdSizeBelowHardwareLimit() {\n    if (++decodesSinceLastFdCheck >= MINIMUM_DECODES_BETWEEN_FD_CHECKS) {\n      decodesSinceLastFdCheck = 0;\n      int currentFds = FD_SIZE_LIST.list().length;\n      long maxFdCount = getMaxFdCount();\n      isFdSizeBelowHardwareLimit = currentFds < maxFdCount;\n\n      if (!isFdSizeBelowHardwareLimit && Log.isLoggable(Downsampler.TAG, Log.WARN)) {\n        Log.w(\n            Downsampler.TAG,\n            \"Excluding HARDWARE bitmap config because we're over the file descriptor limit\"\n                + \", file descriptors \"\n                + currentFds\n                + \", limit \"\n                + maxFdCount);\n      }\n    }\n\n    return isFdSizeBelowHardwareLimit;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageReader.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.BitmapFactory.Options;\nimport android.os.ParcelFileDescriptor;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.ImageHeaderParserUtils;\nimport com.bumptech.glide.load.data.DataRewinder;\nimport com.bumptech.glide.load.data.InputStreamRewinder;\nimport com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.File;\nimport java.io.FileDescriptor;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.util.List;\n\n/**\n * This is a helper class for {@link Downsampler} that abstracts out image operations from the input\n * type wrapped into a {@link DataRewinder}.\n */\ninterface ImageReader {\n\n  @Nullable\n  Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException;\n\n  ImageHeaderParser.ImageType getImageType() throws IOException;\n\n  int getImageOrientation() throws IOException;\n\n  boolean hasJpegMpf() throws IOException;\n\n  void stopGrowingBuffers();\n\n  final class ByteArrayReader implements ImageReader {\n\n    private final byte[] bytes;\n    private final List<ImageHeaderParser> parsers;\n    private final ArrayPool byteArrayPool;\n\n    ByteArrayReader(byte[] bytes, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {\n      this.bytes = bytes;\n      this.parsers = parsers;\n      this.byteArrayPool = byteArrayPool;\n    }\n\n    @Nullable\n    @Override\n    public Bitmap decodeBitmap(Options options) {\n      return GlideBitmapFactory.decodeByteArray(bytes, options, this);\n    }\n\n    @Override\n    public ImageType getImageType() throws IOException {\n      return ImageHeaderParserUtils.getType(parsers, ByteBuffer.wrap(bytes));\n    }\n\n    @Override\n    public int getImageOrientation() throws IOException {\n      return ImageHeaderParserUtils.getOrientation(parsers, ByteBuffer.wrap(bytes), byteArrayPool);\n    }\n\n    @Override\n    public boolean hasJpegMpf() throws IOException {\n      return ImageHeaderParserUtils.hasJpegMpf(parsers, ByteBuffer.wrap(bytes), byteArrayPool);\n    }\n\n    @Override\n    public void stopGrowingBuffers() {}\n  }\n\n  final class FileReader implements ImageReader {\n\n    private final File file;\n    private final List<ImageHeaderParser> parsers;\n    private final ArrayPool byteArrayPool;\n\n    FileReader(File file, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {\n      this.file = file;\n      this.parsers = parsers;\n      this.byteArrayPool = byteArrayPool;\n    }\n\n    @Nullable\n    @Override\n    public Bitmap decodeBitmap(Options options) throws FileNotFoundException {\n      InputStream is = null;\n      try {\n        is = new RecyclableBufferedInputStream(new FileInputStream(file), byteArrayPool);\n        return GlideBitmapFactory.decodeStream(is, options, this);\n      } finally {\n        if (is != null) {\n          try {\n            is.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n      }\n    }\n\n    @Override\n    public ImageType getImageType() throws IOException {\n      InputStream is = null;\n      try {\n        is = new RecyclableBufferedInputStream(new FileInputStream(file), byteArrayPool);\n        return ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);\n      } finally {\n        if (is != null) {\n          try {\n            is.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n      }\n    }\n\n    @Override\n    public int getImageOrientation() throws IOException {\n      InputStream is = null;\n      try {\n        is = new RecyclableBufferedInputStream(new FileInputStream(file), byteArrayPool);\n        return ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);\n      } finally {\n        if (is != null) {\n          try {\n            is.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n      }\n    }\n\n    @Override\n    public boolean hasJpegMpf() throws IOException {\n      InputStream is = null;\n      try {\n        is = new FileInputStream(file);\n        return ImageHeaderParserUtils.hasJpegMpf(parsers, is, byteArrayPool);\n      } finally {\n        if (is != null) {\n          try {\n            is.close();\n          } catch (IOException e) {\n            // Ignored.\n          }\n        }\n      }\n    }\n\n    @Override\n    public void stopGrowingBuffers() {}\n  }\n\n  final class ByteBufferReader implements ImageReader {\n\n    private final ByteBuffer buffer;\n    private final List<ImageHeaderParser> parsers;\n    private final ArrayPool byteArrayPool;\n\n    ByteBufferReader(ByteBuffer buffer, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {\n      this.buffer = buffer;\n      this.parsers = parsers;\n      this.byteArrayPool = byteArrayPool;\n    }\n\n    @Nullable\n    @Override\n    public Bitmap decodeBitmap(Options options) {\n      InputStream inputStream = stream();\n      return GlideBitmapFactory.decodeStream(inputStream, options, this);\n    }\n\n    @Override\n    public ImageType getImageType() throws IOException {\n      return ImageHeaderParserUtils.getType(parsers, ByteBufferUtil.rewind(buffer));\n    }\n\n    @Override\n    public int getImageOrientation() throws IOException {\n      return ImageHeaderParserUtils.getOrientation(\n          parsers, ByteBufferUtil.rewind(buffer), byteArrayPool);\n    }\n\n    @Override\n    public boolean hasJpegMpf() throws IOException {\n      return ImageHeaderParserUtils.hasJpegMpf(\n          parsers, ByteBufferUtil.rewind(buffer), byteArrayPool);\n    }\n\n    @Override\n    public void stopGrowingBuffers() {}\n\n    private InputStream stream() {\n      return ByteBufferUtil.toStream(ByteBufferUtil.rewind(buffer));\n    }\n  }\n\n  final class InputStreamImageReader implements ImageReader {\n    private final InputStreamRewinder dataRewinder;\n    private final ArrayPool byteArrayPool;\n    private final List<ImageHeaderParser> parsers;\n\n    InputStreamImageReader(\n        InputStream is, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {\n      this.byteArrayPool = Preconditions.checkNotNull(byteArrayPool);\n      this.parsers = Preconditions.checkNotNull(parsers);\n\n      dataRewinder = new InputStreamRewinder(is, byteArrayPool);\n    }\n\n    @Nullable\n    @Override\n    public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {\n      InputStream inputStream = dataRewinder.rewindAndGet();\n      return GlideBitmapFactory.decodeStream(inputStream, options, this);\n    }\n\n    @Override\n    public ImageHeaderParser.ImageType getImageType() throws IOException {\n      return ImageHeaderParserUtils.getType(parsers, dataRewinder.rewindAndGet(), byteArrayPool);\n    }\n\n    @Override\n    public int getImageOrientation() throws IOException {\n      return ImageHeaderParserUtils.getOrientation(\n          parsers, dataRewinder.rewindAndGet(), byteArrayPool);\n    }\n\n    @Override\n    public boolean hasJpegMpf() throws IOException {\n      return ImageHeaderParserUtils.hasJpegMpf(parsers, dataRewinder.rewindAndGet(), byteArrayPool);\n    }\n\n    @Override\n    public void stopGrowingBuffers() {\n      dataRewinder.fixMarkLimits();\n    }\n  }\n\n  final class ParcelFileDescriptorImageReader implements ImageReader {\n    private final ArrayPool byteArrayPool;\n    private final List<ImageHeaderParser> parsers;\n    private final ParcelFileDescriptorRewinder dataRewinder;\n\n    ParcelFileDescriptorImageReader(\n        ParcelFileDescriptor parcelFileDescriptor,\n        List<ImageHeaderParser> parsers,\n        ArrayPool byteArrayPool) {\n      this.byteArrayPool = Preconditions.checkNotNull(byteArrayPool);\n      this.parsers = Preconditions.checkNotNull(parsers);\n\n      dataRewinder = new ParcelFileDescriptorRewinder(parcelFileDescriptor);\n    }\n\n    @Nullable\n    @Override\n    public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {\n      ParcelFileDescriptor parcelFileDescriptor = dataRewinder.rewindAndGet();\n      FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();\n      return GlideBitmapFactory.decodeFileDescriptor(fileDescriptor, options, this);\n    }\n\n    @Override\n    public ImageHeaderParser.ImageType getImageType() throws IOException {\n      return ImageHeaderParserUtils.getType(parsers, dataRewinder, byteArrayPool);\n    }\n\n    @Override\n    public int getImageOrientation() throws IOException {\n      return ImageHeaderParserUtils.getOrientation(parsers, dataRewinder, byteArrayPool);\n    }\n\n    @Override\n    public boolean hasJpegMpf() throws IOException {\n      return ImageHeaderParserUtils.hasJpegMpf(parsers, dataRewinder, byteArrayPool);\n    }\n\n    @Override\n    public void stopGrowingBuffers() {\n      // Nothing to do here.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/InputStreamBitmapImageDecoderResourceDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.ImageDecoder;\nimport android.graphics.ImageDecoder.Source;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\n\n/** {@link InputStream} specific implementation of {@link BitmapImageDecoderResourceDecoder}. */\n@RequiresApi(api = 28)\npublic final class InputStreamBitmapImageDecoderResourceDecoder\n    implements ResourceDecoder<InputStream, Bitmap> {\n  private final BitmapImageDecoderResourceDecoder wrapped = new BitmapImageDecoderResourceDecoder();\n\n  @Override\n  public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {\n    return true;\n  }\n\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull InputStream stream, int width, int height, @NonNull Options options)\n      throws IOException {\n    ByteBuffer buffer = ByteBufferUtil.fromStream(stream);\n    Source source = ImageDecoder.createSource(buffer);\n    return wrapped.decode(source, width, height, options);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/LazyBitmapDrawableResource.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.engine.Initializable;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * Lazily allocates a {@link android.graphics.drawable.BitmapDrawable} from a given {@link\n * android.graphics.Bitmap} on the first call to {@link #get()}.\n */\npublic final class LazyBitmapDrawableResource implements Resource<BitmapDrawable>, Initializable {\n\n  private final Resources resources;\n  private final Resource<Bitmap> bitmapResource;\n\n  /**\n   * @deprecated Use {@link #obtain(Resources, Resource)} instead, it can be unsafe to extract\n   *     {@link Bitmap}s from their wrapped {@link Resource}.\n   */\n  @Deprecated\n  public static LazyBitmapDrawableResource obtain(Context context, Bitmap bitmap) {\n    return (LazyBitmapDrawableResource)\n        obtain(\n            context.getResources(),\n            BitmapResource.obtain(bitmap, Glide.get(context).getBitmapPool()));\n  }\n\n  /**\n   * @deprecated Use {@link #obtain(Resources, Resource)} instead, it can be unsafe to extract\n   *     {@link Bitmap}s from their wrapped {@link Resource}.\n   */\n  @Deprecated\n  public static LazyBitmapDrawableResource obtain(\n      Resources resources, BitmapPool bitmapPool, Bitmap bitmap) {\n    return (LazyBitmapDrawableResource)\n        obtain(resources, BitmapResource.obtain(bitmap, bitmapPool));\n  }\n\n  @Nullable\n  public static Resource<BitmapDrawable> obtain(\n      @NonNull Resources resources, @Nullable Resource<Bitmap> bitmapResource) {\n    if (bitmapResource == null) {\n      return null;\n    }\n    return new LazyBitmapDrawableResource(resources, bitmapResource);\n  }\n\n  private LazyBitmapDrawableResource(\n      @NonNull Resources resources, @NonNull Resource<Bitmap> bitmapResource) {\n    this.resources = Preconditions.checkNotNull(resources);\n    this.bitmapResource = Preconditions.checkNotNull(bitmapResource);\n  }\n\n  @NonNull\n  @Override\n  public Class<BitmapDrawable> getResourceClass() {\n    return BitmapDrawable.class;\n  }\n\n  @NonNull\n  @Override\n  public BitmapDrawable get() {\n    return new BitmapDrawable(resources, bitmapResource.get());\n  }\n\n  @Override\n  public int getSize() {\n    return bitmapResource.getSize();\n  }\n\n  @Override\n  public void recycle() {\n    bitmapResource.recycle();\n  }\n\n  @Override\n  public void initialize() {\n    if (bitmapResource instanceof Initializable) {\n      ((Initializable) bitmapResource).initialize();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/ParcelFileDescriptorBitmapDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport android.os.ParcelFileDescriptor;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.io.IOException;\n\n/** Decodes {@link Bitmap}s from {@link ParcelFileDescriptor}s. */\n@RequiresApi(Build.VERSION_CODES.LOLLIPOP)\npublic final class ParcelFileDescriptorBitmapDecoder\n    implements ResourceDecoder<ParcelFileDescriptor, Bitmap> {\n\n  // 512MB. While I don't have data on the number of valid image files > 512mb, I have determined\n  // that virtually all crashes related to Huawei/Honor's DRM checker go away when we don't attempt\n  // to decode files larger than this. We could increase this to 1GB safely, but it seems like 512MB\n  // might be a little better from a crash reduction perspective. See b/201464175.\n  private static final int MAXIMUM_FILE_BYTE_SIZE_FOR_FILE_DESCRIPTOR_DECODER = 512 * 1024 * 1024;\n\n  private final Downsampler downsampler;\n\n  public ParcelFileDescriptorBitmapDecoder(Downsampler downsampler) {\n    this.downsampler = downsampler;\n  }\n\n  @Override\n  public boolean handles(@NonNull ParcelFileDescriptor source, @NonNull Options options) {\n    return isSafeToTryDecoding(source) && downsampler.handles(source);\n  }\n\n  private boolean isSafeToTryDecoding(@NonNull ParcelFileDescriptor source) {\n    if (\"HUAWEI\".equalsIgnoreCase(Build.MANUFACTURER)\n        || \"HONOR\".equalsIgnoreCase(Build.MANUFACTURER)) {\n      return source.getStatSize() <= MAXIMUM_FILE_BYTE_SIZE_FOR_FILE_DESCRIPTOR_DECODER;\n    }\n    return true;\n  }\n\n  @Nullable\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull ParcelFileDescriptor source, int width, int height, @NonNull Options options)\n      throws IOException {\n    return downsampler.decode(source, width, height, options);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\n/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  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\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Wraps an existing {@link InputStream} and <em>buffers</em> the input. Expensive interaction with\n * the underlying input stream is minimized, since most (smaller) requests can be satisfied by\n * accessing the buffer alone. The drawback is that some extra space is required to hold the buffer\n * and that copying takes place when filling that buffer, but this is usually outweighed by the\n * performance benefits.\n *\n * <p>A typical application pattern for the class looks like this:\n *\n * <pre>\n * BufferedInputStream buf = new BufferedInputStream(new FileInputStream(\"file.java\"));\n * </pre>\n */\npublic class RecyclableBufferedInputStream extends FilterInputStream {\n  /** The buffer containing the current bytes read from the target InputStream. */\n  private volatile byte[] buf;\n\n  /** The total number of bytes inside the byte array {@code buf}. */\n  private int count;\n\n  /** The current limit, which when passed, invalidates the current mark. */\n  private int marklimit;\n\n  /**\n   * The currently marked position. -1 indicates no mark has been put or the mark has been\n   * invalidated.\n   */\n  private int markpos = -1;\n\n  /** The current position within the byte array {@code buf}. */\n  private int pos;\n\n  private final ArrayPool byteArrayPool;\n\n  public RecyclableBufferedInputStream(@NonNull InputStream in, @NonNull ArrayPool byteArrayPool) {\n    this(in, byteArrayPool, ArrayPool.STANDARD_BUFFER_SIZE_BYTES);\n  }\n\n  @VisibleForTesting\n  RecyclableBufferedInputStream(\n      @NonNull InputStream in, @NonNull ArrayPool byteArrayPool, int bufferSize) {\n    super(in);\n    this.byteArrayPool = byteArrayPool;\n    buf = byteArrayPool.get(bufferSize, byte[].class);\n  }\n\n  /**\n   * Returns an estimated number of bytes that can be read or skipped without blocking for more\n   * input. This method returns the number of bytes available in the buffer plus those available in\n   * the source stream, but see {@link InputStream#available} for important caveats.\n   *\n   * @return the estimated number of bytes available\n   * @throws IOException if this stream is closed or an error occurs\n   */\n  @Override\n  public synchronized int available() throws IOException {\n    // in could be invalidated by close().\n    InputStream localIn = in;\n    if (buf == null || localIn == null) {\n      throw streamClosed();\n    }\n    return count - pos + localIn.available();\n  }\n\n  private static IOException streamClosed() throws IOException {\n    throw new IOException(\"BufferedInputStream is closed\");\n  }\n\n  /**\n   * Reduces the mark limit to match the current buffer length to prevent the buffer from continuing\n   * to increase in size.\n   *\n   * <p>Subsequent calls to {@link #mark(int)} will be obeyed and may cause the buffer size to\n   * increase.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public synchronized void fixMarkLimit() {\n    marklimit = buf.length;\n  }\n\n  public synchronized void release() {\n    if (buf != null) {\n      byteArrayPool.put(buf);\n      buf = null;\n    }\n  }\n\n  /**\n   * Closes this stream. The source stream is closed and any resources associated with it are\n   * released.\n   *\n   * @throws IOException if an error occurs while closing this stream.\n   */\n  @Override\n  public void close() throws IOException {\n    if (buf != null) {\n      byteArrayPool.put(buf);\n      buf = null;\n    }\n    InputStream localIn = in;\n    in = null;\n    if (localIn != null) {\n      localIn.close();\n    }\n  }\n\n  private int fillbuf(InputStream localIn, byte[] localBuf) throws IOException {\n    if (markpos == -1 || pos - markpos >= marklimit) {\n      // Mark position not put or exceeded readlimit\n      int result = localIn.read(localBuf);\n      if (result > 0) {\n        markpos = -1;\n        pos = 0;\n        count = result;\n      }\n      return result;\n    }\n    // Added count == localBuf.length so that we do not immediately double the buffer size before\n    // reading any data\n    // when marklimit > localBuf.length. Instead, we will double the buffer size only after\n    // reading the initial\n    // localBuf worth of data without finding what we're looking for in the stream. This allows\n    // us to put a\n    // relatively small initial buffer size and a large marklimit for safety without causing an\n    // allocation each time\n    // read is called.\n    if (markpos == 0 && marklimit > localBuf.length && count == localBuf.length) {\n      // Increase buffer size to accommodate the readlimit\n      int newLength = localBuf.length * 2;\n      if (newLength > marklimit) {\n        newLength = marklimit;\n      }\n      byte[] newbuf = byteArrayPool.get(newLength, byte[].class);\n      System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length);\n      byte[] oldbuf = localBuf;\n      // Reassign buf, which will invalidate any local references\n      // FIXME: what if buf was null?\n      localBuf = buf = newbuf;\n      byteArrayPool.put(oldbuf);\n    } else if (markpos > 0) {\n      System.arraycopy(localBuf, markpos, localBuf, 0, localBuf.length - markpos);\n    }\n    // Set the new position and mark position\n    pos -= markpos;\n    count = markpos = 0;\n    int bytesread = localIn.read(localBuf, pos, localBuf.length - pos);\n    count = bytesread <= 0 ? pos : pos + bytesread;\n    return bytesread;\n  }\n\n  /**\n   * Sets a mark position in this stream. The parameter {@code readlimit} indicates how many bytes\n   * can be read before a mark is invalidated. Calling {@link #reset()} will reposition the stream\n   * back to the marked position if {@code readlimit} has not been surpassed. The underlying buffer\n   * may be increased in size to allow {@code readlimit} number of bytes to be supported.\n   *\n   * @param readlimit the number of bytes that can be read before the mark is invalidated.\n   * @see #reset()\n   */\n  @Override\n  public synchronized void mark(int readlimit) {\n    // This is stupid, but BitmapFactory.decodeStream calls mark(1024)\n    // which is too small for a substantial portion of images. This\n    // change (using Math.max) ensures that we don't overwrite readlimit\n    // with a smaller value\n    marklimit = Math.max(marklimit, readlimit);\n    markpos = pos;\n  }\n\n  /**\n   * Indicates whether {@code BufferedInputStream} supports the {@link #mark(int)} and {@link\n   * #reset()} methods.\n   *\n   * @return {@code true} for BufferedInputStreams.\n   * @see #mark(int)\n   * @see #reset()\n   */\n  @Override\n  public boolean markSupported() {\n    return true;\n  }\n\n  /**\n   * Reads a single byte from this stream and returns it as an integer in the range from 0 to 255.\n   * Returns -1 if the end of the source string has been reached. If the internal buffer does not\n   * contain any available bytes then it is filled from the source stream and the first byte is\n   * returned.\n   *\n   * @return the byte read or -1 if the end of the source stream has been reached.\n   * @throws IOException if this stream is closed or another IOException occurs.\n   */\n  @Override\n  public synchronized int read() throws IOException {\n    // Use local refs since buf and in may be invalidated by an\n    // unsynchronized close()\n    byte[] localBuf = buf;\n    InputStream localIn = in;\n    if (localBuf == null || localIn == null) {\n      throw streamClosed();\n    }\n\n    // Are there buffered bytes available?\n    if (pos >= count && fillbuf(localIn, localBuf) == -1) {\n      // no, fill buffer\n      return -1;\n    }\n    // localBuf may have been invalidated by fillbuf\n    if (localBuf != buf) {\n      localBuf = buf;\n      if (localBuf == null) {\n        throw streamClosed();\n      }\n    }\n\n    // Did filling the buffer fail with -1 (EOF)?\n    if (count - pos > 0) {\n      return localBuf[pos++] & 0xFF;\n    }\n    return -1;\n  }\n\n  /**\n   * Reads at most {@code byteCount} bytes from this stream and stores them in byte array {@code\n   * buffer} starting at offset {@code offset}. Returns the number of bytes actually read or -1 if\n   * no bytes were read and the end of the stream was encountered. If all the buffered bytes have\n   * been used, a mark has not been put and the requested number of bytes is larger than the\n   * receiver's buffer size, this implementation bypasses the buffer and simply places the results\n   * directly into {@code buffer}.\n   *\n   * @param buffer the byte array in which to store the bytes read.\n   * @return the number of bytes actually read or -1 if end of stream.\n   * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code byteCount < 0}, or if {@code\n   *     offset + byteCount} is greater than the size of {@code buffer}.\n   * @throws IOException if the stream is already closed or another IOException occurs.\n   */\n  @Override\n  public synchronized int read(@NonNull byte[] buffer, int offset, int byteCount)\n      throws IOException {\n    // Use local ref since buf may be invalidated by an unsynchronized close()\n    byte[] localBuf = buf;\n    if (localBuf == null) {\n      throw streamClosed();\n    }\n    // Arrays.checkOffsetAndCount(buffer.length, offset, byteCount);\n    if (byteCount == 0) {\n      return 0;\n    }\n    InputStream localIn = in;\n    if (localIn == null) {\n      throw streamClosed();\n    }\n\n    int required;\n    if (pos < count) {\n      // There are bytes available in the buffer.\n      int copylength = count - pos >= byteCount ? byteCount : count - pos;\n      System.arraycopy(localBuf, pos, buffer, offset, copylength);\n      pos += copylength;\n      if (copylength == byteCount || localIn.available() == 0) {\n        return copylength;\n      }\n      offset += copylength;\n      required = byteCount - copylength;\n    } else {\n      required = byteCount;\n    }\n\n    while (true) {\n      int read;\n      // If we're not marked and the required size is greater than the buffer,\n      // simply read the bytes directly bypassing the buffer.\n      if (markpos == -1 && required >= localBuf.length) {\n        read = localIn.read(buffer, offset, required);\n        if (read == -1) {\n          return required == byteCount ? -1 : byteCount - required;\n        }\n      } else {\n        if (fillbuf(localIn, localBuf) == -1) {\n          return required == byteCount ? -1 : byteCount - required;\n        }\n        // localBuf may have been invalidated by fillbuf\n        if (localBuf != buf) {\n          localBuf = buf;\n          if (localBuf == null) {\n            throw streamClosed();\n          }\n        }\n\n        read = count - pos >= required ? required : count - pos;\n        System.arraycopy(localBuf, pos, buffer, offset, read);\n        pos += read;\n      }\n      required -= read;\n      if (required == 0) {\n        return byteCount;\n      }\n      if (localIn.available() == 0) {\n        return byteCount - required;\n      }\n      offset += read;\n    }\n  }\n\n  /**\n   * Resets this stream to the last marked location.\n   *\n   * @throws IOException if this stream is closed, no mark has been put or the mark is no longer\n   *     valid because more than {@code readlimit} bytes have been read since setting the mark.\n   * @see #mark(int)\n   */\n  @Override\n  public synchronized void reset() throws IOException {\n    if (buf == null) {\n      throw new IOException(\"Stream is closed\");\n    }\n    if (-1 == markpos) {\n      throw new InvalidMarkException(\n          \"Mark has been invalidated, pos: \" + pos + \" markLimit: \" + marklimit);\n    }\n    pos = markpos;\n  }\n\n  /**\n   * Skips {@code byteCount} bytes in this stream. Subsequent calls to {@link #read} will not return\n   * these bytes unless {@link #reset} is used.\n   *\n   * @param byteCount the number of bytes to skip. This method does nothing and returns 0 if {@code\n   *     byteCount} is less than zero.\n   * @return the number of bytes actually skipped.\n   * @throws IOException if this stream is closed or another IOException occurs.\n   */\n  @Override\n  public synchronized long skip(long byteCount) throws IOException {\n    if (byteCount < 1) {\n      return 0;\n    }\n    // Use local refs since buf and in may be invalidated by an unsynchronized close()\n    byte[] localBuf = buf;\n    if (localBuf == null) {\n      throw streamClosed();\n    }\n    InputStream localIn = in;\n    if (localIn == null) {\n      throw streamClosed();\n    }\n\n    if (count - pos >= byteCount) {\n      pos = (int) (pos + byteCount);\n      return byteCount;\n    }\n    // See https://errorprone.info/bugpattern/IntLongMath.\n    long read = (long) count - pos;\n    pos = count;\n\n    if (markpos != -1 && byteCount <= marklimit) {\n      if (fillbuf(localIn, localBuf) == -1) {\n        return read;\n      }\n      if (count - pos >= byteCount - read) {\n        // See https://errorprone.info/bugpattern/NarrowingCompoundAssignment.\n        pos = (int) (pos + byteCount - read);\n        return byteCount;\n      }\n      // Couldn't get all the bytes, skip what we read.\n      read = read + count - pos;\n      pos = count;\n      return read;\n    }\n\n    // We can't skip over the remaining bytes without exceeding the mark limit so there will be no\n    // way to reset to a proper position in the stream.\n    long skipped = localIn.skip(byteCount - read);\n    if (skipped > 0) {\n      markpos = -1;\n    }\n    return read + skipped;\n  }\n\n  /**\n   * An exception thrown when a mark can no longer be obeyed because the underlying buffer size is\n   * smaller than the amount of data read after the mark position.\n   */\n  static class InvalidMarkException extends IOException {\n    private static final long serialVersionUID = -4338378848813561757L;\n\n    InvalidMarkException(String detailMessage) {\n      super(detailMessage);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/ResourceBitmapDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.content.ContentResolver;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder;\nimport com.bumptech.glide.request.target.Target;\n\n/**\n * Decodes {@link Bitmap}s from resource ids.\n *\n * <p>The framework will decode some resources as {@link Drawable}s that do not wrap {@link\n * Bitmap}s. This decoder will attempt to return a {@link Bitmap} for those {@link Drawable}s anyway\n * by drawing the {@link Drawable} to a {@link Canvas}s using the {@link Drawable}'s intrinsic\n * bounds or the dimensions provided to {@link #decode(Uri, int, int, Options)}.\n *\n * <p>For non-{@link Bitmap} {@link Drawable}s that return {@code <= 0} for {@link\n * Drawable#getIntrinsicWidth()} and/or {@link Drawable#getIntrinsicHeight()}, this decoder will\n * fail if the width and height provided to {@link #decode(Uri, int, int, Options)} are {@link\n * Target#SIZE_ORIGINAL}.\n */\npublic class ResourceBitmapDecoder implements ResourceDecoder<Uri, Bitmap> {\n\n  private final ResourceDrawableDecoder drawableDecoder;\n  private final BitmapPool bitmapPool;\n\n  public ResourceBitmapDecoder(ResourceDrawableDecoder drawableDecoder, BitmapPool bitmapPool) {\n    this.drawableDecoder = drawableDecoder;\n    this.bitmapPool = bitmapPool;\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri source, @NonNull Options options) {\n    return ContentResolver.SCHEME_ANDROID_RESOURCE.equals(source.getScheme());\n  }\n\n  @Nullable\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull Uri source, int width, int height, @NonNull Options options) {\n    Resource<Drawable> drawableResource = drawableDecoder.decode(source, width, height, options);\n    if (drawableResource == null) {\n      return null;\n    }\n    Drawable drawable = drawableResource.get();\n    return DrawableToBitmapConverter.convert(bitmapPool, drawable, width, height);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/Rotate.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Util;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\n\n/** A {@link BitmapTransformation} which rotates the bitmap. */\npublic class Rotate extends BitmapTransformation {\n  private static final String ID = \"com.bumptech.glide.load.resource.bitmap.Rotate\";\n  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);\n\n  private final int degreesToRotate;\n\n  /**\n   * @param degreesToRotate number of degrees to rotate the image by. If zero the original image is\n   *     not modified.\n   */\n  public Rotate(int degreesToRotate) {\n    this.degreesToRotate = degreesToRotate;\n  }\n\n  @Override\n  protected Bitmap transform(\n      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n    return TransformationUtils.rotateImage(toTransform, degreesToRotate);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof Rotate) {\n      Rotate other = (Rotate) o;\n      return degreesToRotate == other.degreesToRotate;\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return Util.hashCode(ID.hashCode(), Util.hashCode(degreesToRotate));\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(ID_BYTES);\n\n    byte[] degreesData = ByteBuffer.allocate(4).putInt(degreesToRotate).array();\n    messageDigest.update(degreesData);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/RoundedCorners.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Util;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\n\n/** A {@link BitmapTransformation} which rounds the corners of a bitmap. */\npublic final class RoundedCorners extends BitmapTransformation {\n  private static final String ID = \"com.bumptech.glide.load.resource.bitmap.RoundedCorners\";\n  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);\n\n  private final int roundingRadius;\n\n  /**\n   * @param roundingRadius the corner radius (in device-specific pixels).\n   * @throws IllegalArgumentException if rounding radius is 0 or less.\n   */\n  public RoundedCorners(int roundingRadius) {\n    Preconditions.checkArgument(roundingRadius > 0, \"roundingRadius must be greater than 0.\");\n    this.roundingRadius = roundingRadius;\n  }\n\n  @Override\n  protected Bitmap transform(\n      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n    return TransformationUtils.roundedCorners(pool, toTransform, roundingRadius);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof RoundedCorners) {\n      RoundedCorners other = (RoundedCorners) o;\n      return roundingRadius == other.roundingRadius;\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return Util.hashCode(ID.hashCode(), Util.hashCode(roundingRadius));\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(ID_BYTES);\n\n    byte[] radiusData = ByteBuffer.allocate(4).putInt(roundingRadius).array();\n    messageDigest.update(radiusData);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.ExceptionPassthroughInputStream;\nimport com.bumptech.glide.util.MarkEnforcingInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Decodes {@link android.graphics.Bitmap Bitmaps} from {@link java.io.InputStream InputStreams}.\n */\npublic class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {\n\n  private final Downsampler downsampler;\n  private final ArrayPool byteArrayPool;\n\n  public StreamBitmapDecoder(Downsampler downsampler, ArrayPool byteArrayPool) {\n    this.downsampler = downsampler;\n    this.byteArrayPool = byteArrayPool;\n  }\n\n  @Override\n  public boolean handles(@NonNull InputStream source, @NonNull Options options) {\n    return downsampler.handles(source);\n  }\n\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull InputStream source, int width, int height, @NonNull Options options)\n      throws IOException {\n\n    // Use to fix the mark limit to avoid allocating buffers that fit entire images.\n    final RecyclableBufferedInputStream bufferedStream;\n    final boolean ownsBufferedStream;\n    if (source instanceof RecyclableBufferedInputStream) {\n      bufferedStream = (RecyclableBufferedInputStream) source;\n      ownsBufferedStream = false;\n    } else {\n      bufferedStream = new RecyclableBufferedInputStream(source, byteArrayPool);\n      ownsBufferedStream = true;\n    }\n\n    // Use to retrieve exceptions thrown while reading.\n    // TODO(#126): when the framework no longer returns partially decoded Bitmaps or provides a\n    // way to determine if a Bitmap is partially decoded, consider removing.\n    ExceptionPassthroughInputStream exceptionStream =\n        ExceptionPassthroughInputStream.obtain(bufferedStream);\n\n    // Use to read data.\n    // Ensures that we can always reset after reading an image header so that we can still\n    // attempt to decode the full image even when the header decode fails and/or overflows our read\n    // buffer. See #283.\n    MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);\n    UntrustedCallbacks callbacks = new UntrustedCallbacks(bufferedStream, exceptionStream);\n    try {\n      return downsampler.decode(invalidatingStream, width, height, options, callbacks);\n    } finally {\n      exceptionStream.release();\n      if (ownsBufferedStream) {\n        bufferedStream.release();\n      }\n    }\n  }\n\n  /**\n   * Callbacks that provide reasonable handling for streams that may be unbuffered or insufficiently\n   * buffered or that may throw exceptions during decoding.\n   */\n  static class UntrustedCallbacks implements Downsampler.DecodeCallbacks {\n    private final RecyclableBufferedInputStream bufferedStream;\n    private final ExceptionPassthroughInputStream exceptionStream;\n\n    UntrustedCallbacks(\n        RecyclableBufferedInputStream bufferedStream,\n        ExceptionPassthroughInputStream exceptionStream) {\n      this.bufferedStream = bufferedStream;\n      this.exceptionStream = exceptionStream;\n    }\n\n    @Override\n    public void onObtainBounds() {\n      // Once we've read the image header, we no longer need to allow the buffer to expand in\n      // size. To avoid unnecessary allocations reading image data, we fix the mark limit so that it\n      // is no larger than our current buffer size here. See issue #225.\n      bufferedStream.fixMarkLimit();\n    }\n\n    @Override\n    public void onDecodeComplete(BitmapPool bitmapPool, Bitmap downsampled) throws IOException {\n      // BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non\n      // null, may catch and log a stack trace but still return a non null bitmap. To avoid\n      // displaying partially decoded bitmaps, we catch exceptions reading from the stream in our\n      // ExceptionCatchingInputStream and throw them here.\n      IOException streamException = exceptionStream.getException();\n      if (streamException != null) {\n        if (downsampled != null) {\n          bitmapPool.put(downsampled);\n        }\n        throw streamException;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\nimport android.graphics.BitmapShader;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffXfermode;\nimport android.graphics.RectF;\nimport android.graphics.Shader;\nimport android.os.Build;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport androidx.exifinterface.media.ExifInterface;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Condition;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/** A class with methods to efficiently resize Bitmaps. */\n// Legacy Public APIs.\n@SuppressWarnings(\"WeakerAccess\")\npublic final class TransformationUtils {\n  private static final String TAG = \"TransformationUtils\";\n  public static final int PAINT_FLAGS = Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG;\n  private static final Paint DEFAULT_PAINT = new Paint(PAINT_FLAGS);\n  private static final int CIRCLE_CROP_PAINT_FLAGS = PAINT_FLAGS | Paint.ANTI_ALIAS_FLAG;\n  private static final Paint CIRCLE_CROP_SHAPE_PAINT = new Paint(CIRCLE_CROP_PAINT_FLAGS);\n  private static final Paint CIRCLE_CROP_BITMAP_PAINT;\n\n  // See #738.\n  private static final Set<String> MODELS_REQUIRING_BITMAP_LOCK =\n      new HashSet<>(\n          Arrays.asList(\n              // Moto X gen 2\n              \"XT1085\",\n              \"XT1092\",\n              \"XT1093\",\n              \"XT1094\",\n              \"XT1095\",\n              \"XT1096\",\n              \"XT1097\",\n              \"XT1098\",\n              // Moto G gen 1\n              \"XT1031\",\n              \"XT1028\",\n              \"XT937C\",\n              \"XT1032\",\n              \"XT1008\",\n              \"XT1033\",\n              \"XT1035\",\n              \"XT1034\",\n              \"XT939G\",\n              \"XT1039\",\n              \"XT1040\",\n              \"XT1042\",\n              \"XT1045\",\n              // Moto G gen 2\n              \"XT1063\",\n              \"XT1064\",\n              \"XT1068\",\n              \"XT1069\",\n              \"XT1072\",\n              \"XT1077\",\n              \"XT1078\",\n              \"XT1079\"));\n\n  /**\n   * https://github.com/bumptech/glide/issues/738 On some devices, bitmap drawing is not thread\n   * safe. This lock only locks for these specific devices. For other types of devices the lock is\n   * always available and therefore does not impact performance\n   */\n  private static final Lock BITMAP_DRAWABLE_LOCK =\n      MODELS_REQUIRING_BITMAP_LOCK.contains(Build.MODEL) ? new ReentrantLock() : new NoLock();\n\n  static {\n    CIRCLE_CROP_BITMAP_PAINT = new Paint(CIRCLE_CROP_PAINT_FLAGS);\n    CIRCLE_CROP_BITMAP_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));\n  }\n\n  private TransformationUtils() {\n    // Utility class.\n  }\n\n  public static Lock getBitmapDrawableLock() {\n    return BITMAP_DRAWABLE_LOCK;\n  }\n\n  /**\n   * A potentially expensive operation to crop the given Bitmap so that it fills the given\n   * dimensions. This operation is significantly less expensive in terms of memory if a mutable\n   * Bitmap with the given dimensions is passed in as well.\n   *\n   * @param pool The BitmapPool to obtain a bitmap from.\n   * @param inBitmap The Bitmap to resize.\n   * @param width The width in pixels of the final Bitmap.\n   * @param height The height in pixels of the final Bitmap.\n   * @return The resized Bitmap (will be recycled if recycled is not null).\n   */\n  public static Bitmap centerCrop(\n      @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int width, int height) {\n    if (inBitmap.getWidth() == width && inBitmap.getHeight() == height) {\n      return inBitmap;\n    }\n    // From ImageView/Bitmap.createScaledBitmap.\n    final float scale;\n    final float dx;\n    final float dy;\n    Matrix m = new Matrix();\n    if (inBitmap.getWidth() * height > width * inBitmap.getHeight()) {\n      scale = (float) height / (float) inBitmap.getHeight();\n      dx = (width - inBitmap.getWidth() * scale) * 0.5f;\n      dy = 0;\n    } else {\n      scale = (float) width / (float) inBitmap.getWidth();\n      dx = 0;\n      dy = (height - inBitmap.getHeight() * scale) * 0.5f;\n    }\n\n    m.setScale(scale, scale);\n    m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));\n\n    Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap));\n    // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.\n    TransformationUtils.setAlpha(inBitmap, result);\n\n    applyMatrix(inBitmap, result, m);\n    return result;\n  }\n\n  /**\n   * An expensive operation to resize the given Bitmap down so that it fits within the given\n   * dimensions maintain the original proportions.\n   *\n   * @param pool The BitmapPool obtain a bitmap from.\n   * @param inBitmap The Bitmap to shrink.\n   * @param width The width in pixels the final image will fit within.\n   * @param height The height in pixels the final image will fit within.\n   * @return A new Bitmap shrunk to fit within the given dimensions, or toFit if toFit's width or\n   *     height matches the given dimensions and toFit fits within the given dimensions\n   */\n  public static Bitmap fitCenter(\n      @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int width, int height) {\n    if (inBitmap.getWidth() == width && inBitmap.getHeight() == height) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"requested target size matches input, returning input\");\n      }\n      return inBitmap;\n    }\n    final float widthPercentage = width / (float) inBitmap.getWidth();\n    final float heightPercentage = height / (float) inBitmap.getHeight();\n    final float minPercentage = Math.min(widthPercentage, heightPercentage);\n\n    // Round here in case we've decoded exactly the image we want, but take the floor below to\n    // avoid a line of garbage or blank pixels in images.\n    int targetWidth = Math.round(minPercentage * inBitmap.getWidth());\n    int targetHeight = Math.round(minPercentage * inBitmap.getHeight());\n\n    if (inBitmap.getWidth() == targetWidth && inBitmap.getHeight() == targetHeight) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"adjusted target size matches input, returning input\");\n      }\n      return inBitmap;\n    }\n\n    // Take the floor of the target width/height, not round. If the matrix\n    // passed into drawBitmap rounds differently, we want to slightly\n    // overdraw, not underdraw, to avoid artifacts from bitmap reuse.\n    targetWidth = (int) (minPercentage * inBitmap.getWidth());\n    targetHeight = (int) (minPercentage * inBitmap.getHeight());\n\n    Bitmap.Config config = getNonNullConfig(inBitmap);\n    Bitmap toReuse = pool.get(targetWidth, targetHeight, config);\n\n    // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.\n    TransformationUtils.setAlpha(inBitmap, toReuse);\n\n    if (Log.isLoggable(TAG, Log.VERBOSE)) {\n      Log.v(TAG, \"request: \" + width + \"x\" + height);\n      Log.v(TAG, \"toFit:   \" + inBitmap.getWidth() + \"x\" + inBitmap.getHeight());\n      Log.v(TAG, \"toReuse: \" + toReuse.getWidth() + \"x\" + toReuse.getHeight());\n      Log.v(TAG, \"minPct:   \" + minPercentage);\n    }\n\n    Matrix matrix = new Matrix();\n    matrix.setScale(minPercentage, minPercentage);\n    applyMatrix(inBitmap, toReuse, matrix);\n\n    return toReuse;\n  }\n\n  /**\n   * If the Bitmap is smaller or equal to the Target it returns the original size, if not then\n   * {@link #fitCenter(BitmapPool, Bitmap, int, int)} is called instead.\n   *\n   * @param pool The BitmapPool obtain a bitmap from.\n   * @param inBitmap The Bitmap to center.\n   * @param width The width in pixels of the target.\n   * @param height The height in pixels of the target.\n   * @return returns input Bitmap if smaller or equal to target, or toFit if the Bitmap's width or\n   *     height is larger than the given dimensions\n   */\n  public static Bitmap centerInside(\n      @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int width, int height) {\n    if (inBitmap.getWidth() <= width && inBitmap.getHeight() <= height) {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"requested target size larger or equal to input, returning input\");\n      }\n      return inBitmap;\n    } else {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"requested target size too big for input, fit centering instead\");\n      }\n      return fitCenter(pool, inBitmap, width, height);\n    }\n  }\n\n  /**\n   * Sets the alpha of the Bitmap we're going to re-use to the alpha of the Bitmap we're going to\n   * transform. This keeps {@link android.graphics.Bitmap#hasAlpha()}} consistent before and after\n   * the transformation for transformations that don't add or remove transparent pixels.\n   *\n   * @param inBitmap The {@link android.graphics.Bitmap} that will be transformed.\n   * @param outBitmap The {@link android.graphics.Bitmap} that will be returned from the\n   *     transformation.\n   */\n  public static void setAlpha(Bitmap inBitmap, Bitmap outBitmap) {\n    outBitmap.setHasAlpha(inBitmap.hasAlpha());\n  }\n\n  /**\n   * This is an expensive operation that copies the image in place with the pixels rotated. If\n   * possible rather use getOrientationMatrix, and put that as the imageMatrix on an ImageView.\n   *\n   * @param imageToOrient Image Bitmap to orient.\n   * @param degreesToRotate number of degrees to rotate the image by. If zero the original image is\n   *     returned unmodified.\n   * @return The oriented bitmap. May be the imageToOrient without modification, or a new Bitmap.\n   */\n  public static Bitmap rotateImage(@NonNull Bitmap imageToOrient, int degreesToRotate) {\n    Bitmap result = imageToOrient;\n    try {\n      if (degreesToRotate != 0) {\n        Matrix matrix = new Matrix();\n        matrix.setRotate(degreesToRotate);\n        result =\n            Bitmap.createBitmap(\n                imageToOrient,\n                0,\n                0,\n                imageToOrient.getWidth(),\n                imageToOrient.getHeight(),\n                matrix,\n                true /*filter*/);\n      }\n    } catch (Exception e) {\n      if (Log.isLoggable(TAG, Log.ERROR)) {\n        Log.e(TAG, \"Exception when trying to orient image\", e);\n      }\n    }\n    return result;\n  }\n\n  /**\n   * Get the # of degrees an image must be rotated to match the given exif orientation.\n   *\n   * @param exifOrientation The exif orientation [1-8]\n   * @return the number of degrees to rotate\n   */\n  public static int getExifOrientationDegrees(int exifOrientation) {\n    final int degreesToRotate;\n    switch (exifOrientation) {\n      case ExifInterface.ORIENTATION_TRANSPOSE:\n      case ExifInterface.ORIENTATION_ROTATE_90:\n        degreesToRotate = 90;\n        break;\n      case ExifInterface.ORIENTATION_ROTATE_180:\n      case ExifInterface.ORIENTATION_FLIP_VERTICAL:\n        degreesToRotate = 180;\n        break;\n      case ExifInterface.ORIENTATION_TRANSVERSE:\n      case ExifInterface.ORIENTATION_ROTATE_270:\n        degreesToRotate = 270;\n        break;\n      default:\n        degreesToRotate = 0;\n        break;\n    }\n    return degreesToRotate;\n  }\n\n  /**\n   * Rotate and/or flip the image to match the given exif orientation.\n   *\n   * @param pool A pool that may or may not contain an image of the necessary dimensions.\n   * @param inBitmap The bitmap to rotate/flip.\n   * @param exifOrientation the exif orientation [1-8].\n   * @return The rotated and/or flipped image or toOrient if no rotation or flip was necessary.\n   */\n  public static Bitmap rotateImageExif(\n      @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int exifOrientation) {\n    if (!isExifOrientationRequired(exifOrientation)) {\n      return inBitmap;\n    }\n\n    final Matrix matrix = new Matrix();\n    initializeMatrixForRotation(exifOrientation, matrix);\n\n    // BitmapPool doesn't preserve gainmaps and color space, so use Bitmap.create to apply the\n    // matrix.\n    return Bitmap.createBitmap(\n        inBitmap,\n        /* x= */ 0,\n        /* y= */ 0,\n        inBitmap.getWidth(),\n        inBitmap.getHeight(),\n        matrix,\n        /* filter= */ true);\n  }\n\n  /**\n   * Returns {@code true} if the given exif orientation indicates that a transformation is necessary\n   * and {@code false} otherwise.\n   */\n  public static boolean isExifOrientationRequired(int exifOrientation) {\n    switch (exifOrientation) {\n      case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:\n      case ExifInterface.ORIENTATION_ROTATE_180:\n      case ExifInterface.ORIENTATION_FLIP_VERTICAL:\n      case ExifInterface.ORIENTATION_TRANSPOSE:\n      case ExifInterface.ORIENTATION_ROTATE_90:\n      case ExifInterface.ORIENTATION_TRANSVERSE:\n      case ExifInterface.ORIENTATION_ROTATE_270:\n        return true;\n      default:\n        return false;\n    }\n  }\n\n  /**\n   * Crop the image to a circle and resize to the specified width/height. The circle crop will have\n   * the same width and height equal to the min-edge of the result image.\n   *\n   * @param pool The BitmapPool obtain a bitmap from.\n   * @param inBitmap The Bitmap to resize.\n   * @param destWidth The width in pixels of the final Bitmap.\n   * @param destHeight The height in pixels of the final Bitmap.\n   * @return The resized Bitmap (will be recycled if recycled is not null).\n   */\n  public static Bitmap circleCrop(\n      @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int destWidth, int destHeight) {\n    int destMinEdge = Math.min(destWidth, destHeight);\n    float radius = destMinEdge / 2f;\n\n    int srcWidth = inBitmap.getWidth();\n    int srcHeight = inBitmap.getHeight();\n\n    float scaleX = destMinEdge / (float) srcWidth;\n    float scaleY = destMinEdge / (float) srcHeight;\n    float maxScale = Math.max(scaleX, scaleY);\n\n    float scaledWidth = maxScale * srcWidth;\n    float scaledHeight = maxScale * srcHeight;\n    float left = (destMinEdge - scaledWidth) / 2f;\n    float top = (destMinEdge - scaledHeight) / 2f;\n\n    RectF destRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);\n\n    // Alpha is required for this transformation.\n    Bitmap toTransform = getAlphaSafeBitmap(pool, inBitmap);\n\n    Bitmap.Config outConfig = getAlphaSafeConfig(inBitmap);\n    Bitmap result = pool.get(destMinEdge, destMinEdge, outConfig);\n    result.setHasAlpha(true);\n\n    BITMAP_DRAWABLE_LOCK.lock();\n    try {\n      Canvas canvas = new Canvas(result);\n      // Draw a circle\n      canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT);\n      // Draw the bitmap in the circle\n      canvas.drawBitmap(toTransform, null, destRect, CIRCLE_CROP_BITMAP_PAINT);\n      clear(canvas);\n    } finally {\n      BITMAP_DRAWABLE_LOCK.unlock();\n    }\n\n    if (!toTransform.equals(inBitmap)) {\n      pool.put(toTransform);\n    }\n\n    return result;\n  }\n\n  private static Bitmap getAlphaSafeBitmap(\n      @NonNull BitmapPool pool, @NonNull Bitmap maybeAlphaSafe) {\n    Bitmap.Config safeConfig = getAlphaSafeConfig(maybeAlphaSafe);\n    if (safeConfig.equals(maybeAlphaSafe.getConfig())) {\n      return maybeAlphaSafe;\n    }\n\n    Bitmap argbBitmap = pool.get(maybeAlphaSafe.getWidth(), maybeAlphaSafe.getHeight(), safeConfig);\n    new Canvas(argbBitmap).drawBitmap(maybeAlphaSafe, 0 /*left*/, 0 /*top*/, null /*paint*/);\n\n    // We now own this Bitmap. It's our responsibility to replace it in the pool outside this method\n    // when we're finished with it.\n    return argbBitmap;\n  }\n\n  @NonNull\n  private static Config getAlphaSafeConfig(@NonNull Bitmap inBitmap) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n      // Avoid short circuiting the sdk check.\n      if (Bitmap.Config.RGBA_F16.equals(inBitmap.getConfig())) { // NOPMD\n        return Bitmap.Config.RGBA_F16;\n      }\n    }\n\n    return Bitmap.Config.ARGB_8888;\n  }\n\n  /**\n   * Creates a bitmap from a source bitmap and rounds the corners.\n   *\n   * @param inBitmap the source bitmap to use as a basis for the created bitmap.\n   * @param width the width of the generated bitmap.\n   * @param height the height of the generated bitmap.\n   * @param roundingRadius the corner radius to be applied (in device-specific pixels).\n   * @return a {@link Bitmap} similar to inBitmap but with rounded corners.\n   * @throws IllegalArgumentException if roundingRadius, width or height is 0 or less.\n   * @deprecated Width and height are unused and ignored. Use {@link #roundedCorners(BitmapPool,\n   *     Bitmap, int)} instead.\n   */\n  @Deprecated\n  public static Bitmap roundedCorners(\n      @NonNull BitmapPool pool,\n      @NonNull Bitmap inBitmap,\n      @SuppressWarnings(\"unused\") int width,\n      @SuppressWarnings(\"unused\") int height,\n      int roundingRadius) {\n    return roundedCorners(pool, inBitmap, roundingRadius);\n  }\n\n  /**\n   * Creates a bitmap from a source bitmap and rounds the corners.\n   *\n   * <p>This method does <em>NOT</em> resize the given {@link Bitmap}, it only rounds it's corners.\n   * To both resize and round the corners of an image, consider {@link\n   * com.bumptech.glide.request.RequestOptions#transform(Transformation[])} and/or {@link\n   * com.bumptech.glide.load.MultiTransformation}.\n   *\n   * @param inBitmap the source bitmap to use as a basis for the created bitmap.\n   * @param roundingRadius the corner radius to be applied (in device-specific pixels).\n   * @return a {@link Bitmap} similar to inBitmap but with rounded corners.\n   * @throws IllegalArgumentException if roundingRadius, width or height is 0 or less.\n   */\n  public static Bitmap roundedCorners(\n      @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, final int roundingRadius) {\n    Preconditions.checkArgument(roundingRadius > 0, \"roundingRadius must be greater than 0.\");\n\n    return roundedCorners(\n        pool,\n        inBitmap,\n        new DrawRoundedCornerFn() {\n          @Override\n          public void drawRoundedCorners(Canvas canvas, Paint paint, RectF rect) {\n            canvas.drawRoundRect(rect, roundingRadius, roundingRadius, paint);\n          }\n        });\n  }\n\n  /**\n   * Creates a bitmap from a source bitmap and rounds the corners, applying a potentially different\n   * [X, Y] radius to each corner.\n   *\n   * <p>This method does <em>NOT</em> resize the given {@link Bitmap}, it only rounds it's corners.\n   * To both resize and round the corners of an image, consider {@link\n   * com.bumptech.glide.request.RequestOptions#transform(Transformation[])} and/or {@link\n   * com.bumptech.glide.load.MultiTransformation}.\n   *\n   * @param inBitmap the source bitmap to use as a basis for the created bitmap.\n   * @param topLeft top-left radius\n   * @param topRight top-right radius\n   * @param bottomRight bottom-right radius\n   * @param bottomLeft bottom-left radius\n   * @return a {@link Bitmap} similar to inBitmap but with rounded corners.\n   */\n  public static Bitmap roundedCorners(\n      @NonNull BitmapPool pool,\n      @NonNull Bitmap inBitmap,\n      final float topLeft,\n      final float topRight,\n      final float bottomRight,\n      final float bottomLeft) {\n    return roundedCorners(\n        pool,\n        inBitmap,\n        new DrawRoundedCornerFn() {\n          @Override\n          public void drawRoundedCorners(Canvas canvas, Paint paint, RectF rect) {\n            Path path = new Path();\n            path.addRoundRect(\n                rect,\n                new float[] {\n                  topLeft,\n                  topLeft,\n                  topRight,\n                  topRight,\n                  bottomRight,\n                  bottomRight,\n                  bottomLeft,\n                  bottomLeft\n                },\n                Path.Direction.CW);\n            canvas.drawPath(path, paint);\n          }\n        });\n  }\n\n  private static Bitmap roundedCorners(\n      @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, DrawRoundedCornerFn drawRoundedCornerFn) {\n\n    // Alpha is required for this transformation.\n    Bitmap.Config safeConfig = getAlphaSafeConfig(inBitmap);\n    Bitmap toTransform = getAlphaSafeBitmap(pool, inBitmap);\n    Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), safeConfig);\n\n    result.setHasAlpha(true);\n\n    BitmapShader shader =\n        new BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);\n    Paint paint = new Paint();\n    paint.setAntiAlias(true);\n    paint.setShader(shader);\n    RectF rect = new RectF(0, 0, result.getWidth(), result.getHeight());\n    BITMAP_DRAWABLE_LOCK.lock();\n    try {\n      Canvas canvas = new Canvas(result);\n      canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);\n      drawRoundedCornerFn.drawRoundedCorners(canvas, paint, rect);\n      clear(canvas);\n    } finally {\n      BITMAP_DRAWABLE_LOCK.unlock();\n    }\n\n    if (!toTransform.equals(inBitmap)) {\n      pool.put(toTransform);\n    }\n\n    return result;\n  }\n\n  // Avoids warnings in M+.\n  private static void clear(Canvas canvas) {\n    canvas.setBitmap(null);\n  }\n\n  @NonNull\n  private static Bitmap.Config getNonNullConfig(@NonNull Bitmap bitmap) {\n    return bitmap.getConfig() != null ? bitmap.getConfig() : Bitmap.Config.ARGB_8888;\n  }\n\n  private static void applyMatrix(\n      @NonNull Bitmap inBitmap, @NonNull Bitmap targetBitmap, Matrix matrix) {\n    BITMAP_DRAWABLE_LOCK.lock();\n    try {\n      Canvas canvas = new Canvas(targetBitmap);\n      canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT);\n      clear(canvas);\n    } finally {\n      BITMAP_DRAWABLE_LOCK.unlock();\n    }\n  }\n\n  @VisibleForTesting\n  static void initializeMatrixForRotation(int exifOrientation, Matrix matrix) {\n    switch (exifOrientation) {\n      case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:\n        matrix.setScale(-1, 1);\n        break;\n      case ExifInterface.ORIENTATION_ROTATE_180:\n        matrix.setRotate(180);\n        break;\n      case ExifInterface.ORIENTATION_FLIP_VERTICAL:\n        matrix.setRotate(180);\n        matrix.postScale(-1, 1);\n        break;\n      case ExifInterface.ORIENTATION_TRANSPOSE:\n        matrix.setRotate(90);\n        matrix.postScale(-1, 1);\n        break;\n      case ExifInterface.ORIENTATION_ROTATE_90:\n        matrix.setRotate(90);\n        break;\n      case ExifInterface.ORIENTATION_TRANSVERSE:\n        matrix.setRotate(-90);\n        matrix.postScale(-1, 1);\n        break;\n      case ExifInterface.ORIENTATION_ROTATE_270:\n        matrix.setRotate(-90);\n        break;\n      default:\n        // Do nothing.\n    }\n  }\n\n  /** Convenience function for drawing a rounded bitmap. */\n  private interface DrawRoundedCornerFn {\n\n    void drawRoundedCorners(Canvas canvas, Paint paint, RectF rect);\n  }\n\n  private static final class NoLock implements Lock {\n\n    @Synthetic\n    NoLock() {}\n\n    @Override\n    public void lock() {\n      // do nothing\n    }\n\n    @Override\n    public void lockInterruptibly() throws InterruptedException {\n      // do nothing\n    }\n\n    @Override\n    public boolean tryLock() {\n      return true;\n    }\n\n    @Override\n    public boolean tryLock(long time, @NonNull TimeUnit unit) throws InterruptedException {\n      return true;\n    }\n\n    @Override\n    public void unlock() {\n      // do nothing\n    }\n\n    @NonNull\n    @Override\n    public Condition newCondition() {\n      throw new UnsupportedOperationException(\"Should not be called\");\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/UnitBitmapDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.Util;\n\n/**\n * Passes through a (hopefully) non-owned {@link Bitmap} as a {@link Bitmap} based {@link Resource}\n * so that the given {@link Bitmap} is not recycled.\n */\npublic final class UnitBitmapDecoder implements ResourceDecoder<Bitmap, Bitmap> {\n\n  @Override\n  public boolean handles(@NonNull Bitmap source, @NonNull Options options) {\n    return true;\n  }\n\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull Bitmap source, int width, int height, @NonNull Options options) {\n    return new NonOwnedBitmapResource(source);\n  }\n\n  private static final class NonOwnedBitmapResource implements Resource<Bitmap> {\n\n    private final Bitmap bitmap;\n\n    NonOwnedBitmapResource(@NonNull Bitmap bitmap) {\n      this.bitmap = bitmap;\n    }\n\n    @NonNull\n    @Override\n    public Class<Bitmap> getResourceClass() {\n      return Bitmap.class;\n    }\n\n    @NonNull\n    @Override\n    public Bitmap get() {\n      return bitmap;\n    }\n\n    @Override\n    public int getSize() {\n      return Util.getBitmapByteSize(bitmap);\n    }\n\n    @Override\n    public void recycle() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/VideoBitmapDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.content.Context;\nimport android.os.ParcelFileDescriptor;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\n\n/**\n * An {@link com.bumptech.glide.load.ResourceDecoder} that can decode a thumbnail frame {@link\n * android.graphics.Bitmap} from a {@link android.os.ParcelFileDescriptor} containing a video.\n *\n * @see android.media.MediaMetadataRetriever\n * @deprecated Use {@link VideoDecoder#parcel(BitmapPool)} instead. This class may be removed and\n *     {@link VideoDecoder} may become final in a future version of Glide.\n */\n@Deprecated\npublic class VideoBitmapDecoder extends VideoDecoder<ParcelFileDescriptor> {\n\n  @SuppressWarnings(\"unused\")\n  public VideoBitmapDecoder(Context context) {\n    this(Glide.get(context).getBitmapPool());\n  }\n\n  // Public API\n  @SuppressWarnings(\"WeakerAccess\")\n  public VideoBitmapDecoder(BitmapPool bitmapPool) {\n    super(bitmapPool, new ParcelFileDescriptorInitializer());\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bitmap/VideoDecoder.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport android.annotation.TargetApi;\nimport android.content.res.AssetFileDescriptor;\nimport android.graphics.Bitmap;\nimport android.graphics.Matrix;\nimport android.media.MediaDataSource;\nimport android.media.MediaExtractor;\nimport android.media.MediaFormat;\nimport android.media.MediaMetadataRetriever;\nimport android.os.Build;\nimport android.os.Build.VERSION;\nimport android.os.Build.VERSION_CODES;\nimport android.os.ParcelFileDescriptor;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.request.target.Target;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Decodes video data to Bitmaps from {@link ParcelFileDescriptor}s and {@link\n * AssetFileDescriptor}s.\n *\n * @param <T> The type of data, currently either {@link ParcelFileDescriptor} or {@link\n *     AssetFileDescriptor}.\n */\npublic class VideoDecoder<T> implements ResourceDecoder<T, Bitmap> {\n  private static final String TAG = \"VideoDecoder\";\n\n  /**\n   * A constant indicating we should use whatever frame we consider best, frequently not the first\n   * frame.\n   */\n  public static final long DEFAULT_FRAME = -1;\n\n  /** Matches the behavior of {@link MediaMetadataRetriever#getFrameAtTime(long)}. */\n  @VisibleForTesting\n  static final int DEFAULT_FRAME_OPTION = MediaMetadataRetriever.OPTION_CLOSEST_SYNC;\n\n  /**\n   * A long indicating the time position (in microseconds) of the target frame which will be\n   * retrieved. {@link android.media.MediaMetadataRetriever#getFrameAtTime(long)} is used to extract\n   * the video frame.\n   *\n   * <p>When retrieving the frame at the given time position, there is no guarantee that the data\n   * source has a frame located at the position. When this happens, a frame nearby will be returned.\n   * If the long is negative, time position and option will ignored, and any frame that the\n   * implementation considers as representative may be returned.\n   */\n  public static final Option<Long> TARGET_FRAME =\n      Option.disk(\n          \"com.bumptech.glide.load.resource.bitmap.VideoBitmapDecode.TargetFrame\",\n          DEFAULT_FRAME,\n          new Option.CacheKeyUpdater<Long>() {\n            private final ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);\n\n            @Override\n            public void update(\n                @NonNull byte[] keyBytes,\n                @NonNull Long value,\n                @NonNull MessageDigest messageDigest) {\n              messageDigest.update(keyBytes);\n              synchronized (buffer) {\n                buffer.position(0);\n                messageDigest.update(buffer.putLong(value).array());\n              }\n            }\n          });\n\n  /**\n   * An integer indicating the frame option used to retrieve a target frame.\n   *\n   * <p>This option will be ignored if {@link #TARGET_FRAME} is not set or is set to {@link\n   * #DEFAULT_FRAME}.\n   *\n   * @see MediaMetadataRetriever#getFrameAtTime(long, int)\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static final Option<Integer> FRAME_OPTION =\n      Option.disk(\n          \"com.bumptech.glide.load.resource.bitmap.VideoBitmapDecode.FrameOption\",\n          /* defaultValue= */ MediaMetadataRetriever.OPTION_CLOSEST_SYNC,\n          new Option.CacheKeyUpdater<Integer>() {\n            private final ByteBuffer buffer = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE);\n\n            @Override\n            public void update(\n                @NonNull byte[] keyBytes,\n                @NonNull Integer value,\n                @NonNull MessageDigest messageDigest) {\n              //noinspection ConstantConditions public API, people could have been doing it wrong\n              if (value == null) {\n                return;\n              }\n              messageDigest.update(keyBytes);\n              synchronized (buffer) {\n                buffer.position(0);\n                messageDigest.update(buffer.putInt(value).array());\n              }\n            }\n          });\n\n  private static final MediaMetadataRetrieverFactory DEFAULT_FACTORY =\n      new MediaMetadataRetrieverFactory();\n\n  /**\n   * List of Pixel Android T build id prefixes missing a fix for HDR video with 180 deg rotations\n   * having doubly-rotated thumbnails.\n   *\n   * <p>More recent Android T builds should have the fix.\n   */\n  private static final List<String> PIXEL_T_BUILD_ID_PREFIXES_REQUIRING_HDR_180_ROTATION_FIX =\n      Collections.unmodifiableList(Arrays.asList(\"TP1A\", \"TD1A.220804.031\"));\n\n  private static final String WEBM_MIME_TYPE = \"video/webm\";\n\n  private final MediaInitializer<T> initializer;\n  private final BitmapPool bitmapPool;\n  private final MediaMetadataRetrieverFactory factory;\n\n  @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)\n  public static ResourceDecoder<AssetFileDescriptor, Bitmap> asset(BitmapPool bitmapPool) {\n    return new VideoDecoder<>(bitmapPool, new AssetFileDescriptorInitializer());\n  }\n\n  public static ResourceDecoder<ParcelFileDescriptor, Bitmap> parcel(BitmapPool bitmapPool) {\n    return new VideoDecoder<>(bitmapPool, new ParcelFileDescriptorInitializer());\n  }\n\n  @RequiresApi(api = VERSION_CODES.M)\n  public static ResourceDecoder<ByteBuffer, Bitmap> byteBuffer(BitmapPool bitmapPool) {\n    return new VideoDecoder<>(bitmapPool, new ByteBufferInitializer());\n  }\n\n  VideoDecoder(BitmapPool bitmapPool, MediaInitializer<T> initializer) {\n    this(bitmapPool, initializer, DEFAULT_FACTORY);\n  }\n\n  @VisibleForTesting\n  VideoDecoder(\n      BitmapPool bitmapPool,\n      MediaInitializer<T> initializer,\n      MediaMetadataRetrieverFactory factory) {\n    this.bitmapPool = bitmapPool;\n    this.initializer = initializer;\n    this.factory = factory;\n  }\n\n  @Override\n  public boolean handles(@NonNull T data, @NonNull Options options) {\n    // Calling setDataSource is expensive so avoid doing so unless we're actually called.\n    // For non-videos this isn't any cheaper, but for videos it saves the redundant call and\n    // 50-100ms.\n    return true;\n  }\n\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull T resource, int outWidth, int outHeight, @NonNull Options options)\n      throws IOException {\n    long frameTimeMicros = options.get(TARGET_FRAME);\n    if (frameTimeMicros < 0 && frameTimeMicros != DEFAULT_FRAME) {\n      throw new IllegalArgumentException(\n          \"Requested frame must be non-negative, or DEFAULT_FRAME, given: \" + frameTimeMicros);\n    }\n    Integer frameOption = options.get(FRAME_OPTION);\n    if (frameOption == null) {\n      frameOption = DEFAULT_FRAME_OPTION;\n    }\n    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);\n    if (downsampleStrategy == null) {\n      downsampleStrategy = DownsampleStrategy.DEFAULT;\n    }\n\n    final Bitmap result;\n    MediaMetadataRetriever mediaMetadataRetriever = factory.build();\n    try {\n      initializer.initializeRetriever(mediaMetadataRetriever, resource);\n      result =\n          decodeFrame(\n              resource,\n              mediaMetadataRetriever,\n              frameTimeMicros,\n              frameOption,\n              outWidth,\n              outHeight,\n              downsampleStrategy);\n    } finally {\n      if (Build.VERSION.SDK_INT >= VERSION_CODES.Q) {\n        mediaMetadataRetriever.close();\n      } else {\n        mediaMetadataRetriever.release();\n      }\n    }\n\n    return BitmapResource.obtain(result, bitmapPool);\n  }\n\n  @Nullable\n  private Bitmap decodeFrame(\n      @NonNull T resource,\n      MediaMetadataRetriever mediaMetadataRetriever,\n      long frameTimeMicros,\n      int frameOption,\n      int outWidth,\n      int outHeight,\n      DownsampleStrategy strategy) {\n    if (isUnsupportedFormat(resource, mediaMetadataRetriever)) {\n      throw new IllegalStateException(\"Cannot decode VP8 video on CrOS.\");\n    }\n\n    Bitmap result = null;\n    // Arguably we should handle the case where just width or just height is set to\n    // Target.SIZE_ORIGINAL. Up to and including OMR1, MediaMetadataRetriever defaults to setting\n    // the dimensions to the display width and height if they aren't specified (ie\n    // getScaledFrameAtTime is not used). Given that this is an optimization only if\n    // Target.SIZE_ORIGINAL is not used and not using getScaledFrameAtTime ever would match the\n    // behavior of Glide in all versions of Android prior to OMR1, it's probably fine for now.\n    if (Build.VERSION.SDK_INT >= VERSION_CODES.O_MR1\n        && outWidth != Target.SIZE_ORIGINAL\n        && outHeight != Target.SIZE_ORIGINAL\n        && strategy != DownsampleStrategy.NONE) {\n      result =\n          decodeScaledFrame(\n              mediaMetadataRetriever, frameTimeMicros, frameOption, outWidth, outHeight, strategy);\n    }\n\n    if (result == null) {\n      result = decodeOriginalFrame(mediaMetadataRetriever, frameTimeMicros, frameOption);\n    }\n\n    // MediaMetadataRetriever has a bug where HDR videos with 180 deg rotations are rotated twice,\n    // causing the output frame to appear upside. This needs to be corrected for all versions of\n    // Android until a platform fix lands.\n    result = correctHdr180DegVideoFrameOrientation(mediaMetadataRetriever, result);\n\n    // Throwing an exception works better in our error logging than returning null. It shouldn't\n    // be expensive because video decoders are attempted after image loads. Video errors are often\n    // logged by the framework, so we can also use this error to suggest callers look for the\n    // appropriate tags in adb.\n    if (result == null) {\n      throw new VideoDecoderException();\n    }\n\n    return result;\n  }\n\n  /**\n   * Corrects the orientation of a bitmap extracted from an HDR video with a 180 degree rotation\n   * angle.\n   *\n   * <p>This method will only return a rotated bitmap instead of the input bitmap if\n   *\n   * <ul>\n   *   <li>The Android SDK level is >= R && < T OR the build id is one of T builds without the\n   *       platform fix.\n   *   <li>The video has a color transfer function with an HLG or ST2084 (PQ) transfer function.\n   *   <li>The video has a color standard of BT.2020.\n   *   <li>The video has a rotation angle of +/- 180 degrees.\n   * </ul>\n   */\n  @TargetApi(Build.VERSION_CODES.R)\n  private static Bitmap correctHdr180DegVideoFrameOrientation(\n      MediaMetadataRetriever mediaMetadataRetriever, Bitmap frame) {\n    if (!isHdr180RotationFixRequired()) {\n      return frame;\n    }\n    boolean requiresHdr180RotationFix = false;\n    try {\n      if (isHDR(mediaMetadataRetriever)) {\n        String rotationString =\n            mediaMetadataRetriever.extractMetadata(\n                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);\n        int rotation = Integer.parseInt(rotationString);\n        requiresHdr180RotationFix = Math.abs(rotation) == 180;\n      }\n    } catch (NumberFormatException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Exception trying to extract HDR transfer function or rotation\");\n      }\n    }\n\n    if (!requiresHdr180RotationFix) {\n      return frame;\n    }\n\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      Log.d(TAG, \"Applying HDR 180 deg thumbnail correction\");\n    }\n    Matrix rotationMatrix = new Matrix();\n    rotationMatrix.postRotate(\n        /* degrees= */ 180, frame.getWidth() / 2.0f, frame.getHeight() / 2.0f);\n    return Bitmap.createBitmap(\n        frame,\n        /* x= */ 0,\n        /* y= */ 0,\n        frame.getWidth(),\n        frame.getHeight(),\n        rotationMatrix,\n        /* filter= */ true);\n  }\n\n  @RequiresApi(VERSION_CODES.R)\n  private static boolean isHDR(MediaMetadataRetriever mediaMetadataRetriever)\n      throws NumberFormatException {\n    String colorTransferString =\n        mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COLOR_TRANSFER);\n    String colorStandardString =\n        mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COLOR_STANDARD);\n    int colorTransfer = Integer.parseInt(colorTransferString);\n    int colorStandard = Integer.parseInt(colorStandardString);\n    // This check needs to match the isHDR check in\n    // frameworks/av/media/libstagefright/FrameDecoder.cpp.\n    return (colorTransfer == MediaFormat.COLOR_TRANSFER_HLG\n            || colorTransfer == MediaFormat.COLOR_TRANSFER_ST2084)\n        && colorStandard == MediaFormat.COLOR_STANDARD_BT2020;\n  }\n\n  /** Returns true if the build requires a fix for the HDR 180 degree rotation bug. */\n  @VisibleForTesting\n  static boolean isHdr180RotationFixRequired() {\n    // Only pixel devices have android T builds without the framework fix.\n    if (Build.MODEL.startsWith(\"Pixel\") && VERSION.SDK_INT == VERSION_CODES.TIRAMISU) {\n      return isTBuildRequiringRotationFix();\n    } else {\n      return VERSION.SDK_INT >= VERSION_CODES.R && VERSION.SDK_INT < VERSION_CODES.TIRAMISU;\n    }\n  }\n\n  /**\n   * Returns true if the build is an Android T build that requires a fix for the HDR 180 degree\n   * rotation bug.\n   */\n  private static boolean isTBuildRequiringRotationFix() {\n    for (String buildId : PIXEL_T_BUILD_ID_PREFIXES_REQUIRING_HDR_180_ROTATION_FIX) {\n      if (Build.ID.startsWith(buildId)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  @Nullable\n  @TargetApi(Build.VERSION_CODES.O_MR1)\n  private static Bitmap decodeScaledFrame(\n      MediaMetadataRetriever mediaMetadataRetriever,\n      long frameTimeMicros,\n      int frameOption,\n      int outWidth,\n      int outHeight,\n      DownsampleStrategy strategy) {\n    try {\n      int originalWidth =\n          Integer.parseInt(\n              mediaMetadataRetriever.extractMetadata(\n                  MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));\n      int originalHeight =\n          Integer.parseInt(\n              mediaMetadataRetriever.extractMetadata(\n                  MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));\n      int orientation =\n          Integer.parseInt(\n              mediaMetadataRetriever.extractMetadata(\n                  MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));\n\n      if (orientation == 90 || orientation == 270) {\n        int temp = originalWidth;\n        //noinspection SuspiciousNameCombination\n        originalWidth = originalHeight;\n        originalHeight = temp;\n      }\n\n      float scaleFactor =\n          strategy.getScaleFactor(originalWidth, originalHeight, outWidth, outHeight);\n\n      int decodeWidth = Math.round(scaleFactor * originalWidth);\n      int decodeHeight = Math.round(scaleFactor * originalHeight);\n\n      return mediaMetadataRetriever.getScaledFrameAtTime(\n          frameTimeMicros, frameOption, decodeWidth, decodeHeight);\n    } catch (Throwable t) {\n      // This is aggressive, but we'd rather catch errors caused by reading and/or parsing metadata\n      // here and fall back to just decoding the frame whenever possible. If the exception is thrown\n      // just from decoding the frame, then it will be thrown and exposed to callers by the method\n      // below.\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(\n            TAG,\n            \"Exception trying to decode a scaled frame on oreo+, falling back to a fullsize frame\",\n            t);\n      }\n\n      return null;\n    }\n  }\n\n  private static Bitmap decodeOriginalFrame(\n      MediaMetadataRetriever mediaMetadataRetriever, long frameTimeMicros, int frameOption) {\n    return mediaMetadataRetriever.getFrameAtTime(frameTimeMicros, frameOption);\n  }\n\n  /** Returns true if the format type is unsupported on the device. */\n  private boolean isUnsupportedFormat(\n      @NonNull T resource, MediaMetadataRetriever mediaMetadataRetriever) {\n    // MediaFormat.KEY_MIME check below requires at least JELLY_BEAN\n    if (Build.VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN) {\n      return false;\n    }\n\n    // The primary known problem is vp8 video on ChromeOS (ARC) devices.\n    boolean isArc = Build.DEVICE != null && Build.DEVICE.matches(\".+_cheets|cheets_.+\");\n    if (!isArc) {\n      return false;\n    }\n\n    MediaExtractor mediaExtractor = null;\n    try {\n      // Include the MediaMetadataRetriever extract in the try block out of an abundance of caution.\n      String mimeType =\n          mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE);\n      if (!WEBM_MIME_TYPE.equals(mimeType)) {\n        return false;\n      }\n\n      // Only construct a MediaExtractor for webm files, since the constructor makes a JNI call\n      mediaExtractor = new MediaExtractor();\n      initializer.initializeExtractor(mediaExtractor, resource);\n      int numTracks = mediaExtractor.getTrackCount();\n      for (int i = 0; i < numTracks; ++i) {\n        MediaFormat mediaformat = mediaExtractor.getTrackFormat(i);\n        String trackMimeType = mediaformat.getString(MediaFormat.KEY_MIME);\n        if (MediaFormat.MIMETYPE_VIDEO_VP8.equals(trackMimeType)) {\n          return true;\n        }\n      }\n    } catch (Throwable t) {\n      // Catching everything here out of an abundance of caution\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Exception trying to extract track info for a webm video on CrOS.\", t);\n      }\n    } finally {\n      if (mediaExtractor != null) {\n        mediaExtractor.release();\n      }\n    }\n\n    return false;\n  }\n\n  @VisibleForTesting\n  static class MediaMetadataRetrieverFactory {\n    public MediaMetadataRetriever build() {\n      return new MediaMetadataRetriever();\n    }\n  }\n\n  @VisibleForTesting\n  interface MediaInitializer<T> {\n    void initializeRetriever(MediaMetadataRetriever retriever, T data);\n\n    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)\n    void initializeExtractor(MediaExtractor extractor, T data) throws IOException;\n  }\n\n  @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)\n  private static final class AssetFileDescriptorInitializer\n      implements MediaInitializer<AssetFileDescriptor> {\n\n    @Override\n    public void initializeRetriever(MediaMetadataRetriever retriever, AssetFileDescriptor data) {\n      retriever.setDataSource(data.getFileDescriptor(), data.getStartOffset(), data.getLength());\n    }\n\n    @Override\n    public void initializeExtractor(MediaExtractor extractor, AssetFileDescriptor data)\n        throws IOException {\n      extractor.setDataSource(data.getFileDescriptor(), data.getStartOffset(), data.getLength());\n    }\n  }\n\n  // Visible for VideoBitmapDecoder.\n  static final class ParcelFileDescriptorInitializer\n      implements MediaInitializer<ParcelFileDescriptor> {\n\n    @Override\n    public void initializeRetriever(MediaMetadataRetriever retriever, ParcelFileDescriptor data) {\n      retriever.setDataSource(data.getFileDescriptor());\n    }\n\n    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)\n    @Override\n    public void initializeExtractor(MediaExtractor extractor, ParcelFileDescriptor data)\n        throws IOException {\n      extractor.setDataSource(data.getFileDescriptor());\n    }\n  }\n\n  @RequiresApi(Build.VERSION_CODES.M)\n  static final class ByteBufferInitializer implements MediaInitializer<ByteBuffer> {\n\n    @Override\n    public void initializeRetriever(MediaMetadataRetriever retriever, final ByteBuffer data) {\n      retriever.setDataSource(getMediaDataSource(data));\n    }\n\n    @Override\n    public void initializeExtractor(MediaExtractor extractor, final ByteBuffer data)\n        throws IOException {\n      extractor.setDataSource(getMediaDataSource(data));\n    }\n\n    private MediaDataSource getMediaDataSource(final ByteBuffer data) {\n      return new MediaDataSource() {\n        @Override\n        public int readAt(long position, byte[] buffer, int offset, int size) {\n          if (position >= data.limit()) {\n            return -1;\n          }\n          data.position((int) position);\n          int numBytesRead = Math.min(size, data.remaining());\n          data.get(buffer, offset, numBytesRead);\n          return numBytesRead;\n        }\n\n        @Override\n        public long getSize() {\n          return data.limit();\n        }\n\n        @Override\n        public void close() {}\n      };\n    }\n  }\n\n  private static final class VideoDecoderException extends RuntimeException {\n\n    private static final long serialVersionUID = -2556382523004027815L;\n\n    VideoDecoderException() {\n      super(\n          \"MediaMetadataRetriever failed to retrieve a frame without throwing, check the adb logs\"\n              + \" for .*MetadataRetriever.* prior to this exception for details\");\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bytes/ByteBufferRewinder.java",
    "content": "package com.bumptech.glide.load.resource.bytes;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.data.DataRewinder;\nimport java.nio.ByteBuffer;\n\n/** Rewinds {@link java.nio.ByteBuffer}s. */\npublic class ByteBufferRewinder implements DataRewinder<ByteBuffer> {\n  private final ByteBuffer buffer;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public ByteBufferRewinder(ByteBuffer buffer) {\n    this.buffer = buffer;\n  }\n\n  @NonNull\n  @Override\n  public ByteBuffer rewindAndGet() {\n    buffer.position(0);\n    return buffer;\n  }\n\n  @Override\n  public void cleanup() {\n    // Do nothing.\n  }\n\n  /** Factory for {@link com.bumptech.glide.load.resource.bytes.ByteBufferRewinder}. */\n  public static class Factory implements DataRewinder.Factory<ByteBuffer> {\n\n    @NonNull\n    @Override\n    public DataRewinder<ByteBuffer> build(ByteBuffer data) {\n      return new ByteBufferRewinder(data);\n    }\n\n    @NonNull\n    @Override\n    public Class<ByteBuffer> getDataClass() {\n      return ByteBuffer.class;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/bytes/BytesResource.java",
    "content": "package com.bumptech.glide.load.resource.bytes;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.Preconditions;\n\n/** An {@link com.bumptech.glide.load.engine.Resource} wrapping a byte array. */\npublic class BytesResource implements Resource<byte[]> {\n  private final byte[] bytes;\n\n  public BytesResource(byte[] bytes) {\n    this.bytes = Preconditions.checkNotNull(bytes);\n  }\n\n  @NonNull\n  @Override\n  public Class<byte[]> getResourceClass() {\n    return byte[].class;\n  }\n\n  /**\n   * In most cases it will only be retrieved once (see linked methods).\n   *\n   * @return the same array every time, do not mutate the contents. Not a copy returned, because\n   *     copying the array can be prohibitively expensive and/or lead to OOMs.\n   * @see com.bumptech.glide.load.ResourceEncoder\n   * @see com.bumptech.glide.load.resource.transcode.ResourceTranscoder\n   * @see com.bumptech.glide.request.SingleRequest#onResourceReady\n   */\n  @NonNull\n  @Override\n  @SuppressWarnings(\"PMD.MethodReturnsInternalArray\")\n  public byte[] get() {\n    return bytes;\n  }\n\n  @Override\n  public int getSize() {\n    return bytes.length;\n  }\n\n  @Override\n  public void recycle() {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/drawable/AnimatedImageDecoder.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport android.graphics.Bitmap;\nimport android.graphics.ImageDecoder;\nimport android.graphics.ImageDecoder.Source;\nimport android.graphics.drawable.AnimatedImageDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.ImageHeaderParserUtils;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.resource.DefaultOnHeaderDecodedListener;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.util.List;\n\n/**\n * Allows decoding animated images using {@link ImageDecoder}.\n *\n * <p>Supported formats: WebP on Android P+. AVIF on Android 12/S+.\n */\n@RequiresApi(Build.VERSION_CODES.P)\npublic final class AnimatedImageDecoder {\n  private final List<ImageHeaderParser> imageHeaderParsers;\n  private final ArrayPool arrayPool;\n\n  public static ResourceDecoder<InputStream, Drawable> streamDecoder(\n      List<ImageHeaderParser> imageHeaderParsers, ArrayPool arrayPool) {\n    return new StreamAnimatedImageDecoder(new AnimatedImageDecoder(imageHeaderParsers, arrayPool));\n  }\n\n  public static ResourceDecoder<ByteBuffer, Drawable> byteBufferDecoder(\n      List<ImageHeaderParser> imageHeaderParsers, ArrayPool arrayPool) {\n    return new ByteBufferAnimatedImageDecoder(\n        new AnimatedImageDecoder(imageHeaderParsers, arrayPool));\n  }\n\n  private AnimatedImageDecoder(List<ImageHeaderParser> imageHeaderParsers, ArrayPool arrayPool) {\n    this.imageHeaderParsers = imageHeaderParsers;\n    this.arrayPool = arrayPool;\n  }\n\n  @Synthetic\n  boolean handles(ByteBuffer byteBuffer) throws IOException {\n    return isHandled(ImageHeaderParserUtils.getType(imageHeaderParsers, byteBuffer));\n  }\n\n  @Synthetic\n  boolean handles(InputStream is) throws IOException {\n    return isHandled(ImageHeaderParserUtils.getType(imageHeaderParsers, is, arrayPool));\n  }\n\n  @SuppressWarnings(\"checkstyle:UnnecessaryParentheses\") // Readability\n  private boolean isHandled(ImageType imageType) {\n    return imageType == ImageType.ANIMATED_WEBP\n        || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && imageType == ImageType.ANIMATED_AVIF);\n  }\n\n  @Synthetic\n  Resource<Drawable> decode(@NonNull Source source, int width, int height, @NonNull Options options)\n      throws IOException {\n    Drawable decoded =\n        ImageDecoder.decodeDrawable(\n            source, new DefaultOnHeaderDecodedListener(width, height, options));\n    if (!(decoded instanceof AnimatedImageDrawable)) {\n      throw new IOException(\n          \"Received unexpected drawable type for animated image, failing: \" + decoded);\n    }\n    return new AnimatedImageDrawableResource((AnimatedImageDrawable) decoded);\n  }\n\n  private static final class AnimatedImageDrawableResource implements Resource<Drawable> {\n    /** A totally made up number of the number of frames we think are held in memory at once... */\n    private static final int ESTIMATED_NUMBER_OF_FRAMES = 2;\n\n    private final AnimatedImageDrawable imageDrawable;\n\n    AnimatedImageDrawableResource(AnimatedImageDrawable imageDrawable) {\n      this.imageDrawable = imageDrawable;\n    }\n\n    @NonNull\n    @Override\n    public Class<Drawable> getResourceClass() {\n      return Drawable.class;\n    }\n\n    @NonNull\n    @Override\n    public AnimatedImageDrawable get() {\n      return imageDrawable;\n    }\n\n    @Override\n    public int getSize() {\n      return imageDrawable.getIntrinsicWidth()\n          * imageDrawable.getIntrinsicHeight()\n          * Util.getBytesPerPixel(Bitmap.Config.ARGB_8888)\n          * ESTIMATED_NUMBER_OF_FRAMES;\n    }\n\n    @Override\n    public void recycle() {\n      imageDrawable.stop();\n      imageDrawable.clearAnimationCallbacks();\n    }\n  }\n\n  private static final class StreamAnimatedImageDecoder\n      implements ResourceDecoder<InputStream, Drawable> {\n\n    private final AnimatedImageDecoder delegate;\n\n    StreamAnimatedImageDecoder(AnimatedImageDecoder delegate) {\n      this.delegate = delegate;\n    }\n\n    @Override\n    public boolean handles(@NonNull InputStream source, @NonNull Options options)\n        throws IOException {\n      return delegate.handles(source);\n    }\n\n    @Override\n    public Resource<Drawable> decode(\n        @NonNull InputStream is, int width, int height, @NonNull Options options)\n        throws IOException {\n      Source source = ImageDecoder.createSource(ByteBufferUtil.fromStream(is));\n      return delegate.decode(source, width, height, options);\n    }\n  }\n\n  private static final class ByteBufferAnimatedImageDecoder\n      implements ResourceDecoder<ByteBuffer, Drawable> {\n\n    private final AnimatedImageDecoder delegate;\n\n    ByteBufferAnimatedImageDecoder(AnimatedImageDecoder delegate) {\n      this.delegate = delegate;\n    }\n\n    @Override\n    public boolean handles(@NonNull ByteBuffer source, @NonNull Options options)\n        throws IOException {\n      return delegate.handles(source);\n    }\n\n    @Override\n    public Resource<Drawable> decode(\n        @NonNull ByteBuffer byteBuffer, int width, int height, @NonNull Options options)\n        throws IOException {\n      Source source = ImageDecoder.createSource(byteBuffer);\n      return delegate.decode(source, width, height, options);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/drawable/AnimatedWebpDecoder.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport android.graphics.Bitmap;\nimport android.graphics.ImageDecoder;\nimport android.graphics.ImageDecoder.Source;\nimport android.graphics.drawable.AnimatedImageDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.ImageHeaderParserUtils;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.resource.DefaultOnHeaderDecodedListener;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.util.List;\n\n/**\n * Allows decoding animated webp images using {@link ImageDecoder} on Android P+. @Deprecated This\n * class has been replaced by {@link AnimatedImageDecoder} and is not used in Glide by default. It\n * will be removed in a future version.\n */\n@Deprecated\n@RequiresApi(Build.VERSION_CODES.P)\npublic final class AnimatedWebpDecoder {\n  private final List<ImageHeaderParser> imageHeaderParsers;\n  private final ArrayPool arrayPool;\n\n  public static ResourceDecoder<InputStream, Drawable> streamDecoder(\n      List<ImageHeaderParser> imageHeaderParsers, ArrayPool arrayPool) {\n    return new StreamAnimatedWebpDecoder(new AnimatedWebpDecoder(imageHeaderParsers, arrayPool));\n  }\n\n  public static ResourceDecoder<ByteBuffer, Drawable> byteBufferDecoder(\n      List<ImageHeaderParser> imageHeaderParsers, ArrayPool arrayPool) {\n    return new ByteBufferAnimatedWebpDecoder(\n        new AnimatedWebpDecoder(imageHeaderParsers, arrayPool));\n  }\n\n  private AnimatedWebpDecoder(List<ImageHeaderParser> imageHeaderParsers, ArrayPool arrayPool) {\n    this.imageHeaderParsers = imageHeaderParsers;\n    this.arrayPool = arrayPool;\n  }\n\n  @Synthetic\n  boolean handles(ByteBuffer byteBuffer) throws IOException {\n    return isHandled(ImageHeaderParserUtils.getType(imageHeaderParsers, byteBuffer));\n  }\n\n  @Synthetic\n  boolean handles(InputStream is) throws IOException {\n    return isHandled(ImageHeaderParserUtils.getType(imageHeaderParsers, is, arrayPool));\n  }\n\n  private boolean isHandled(ImageType imageType) {\n    return imageType == ImageType.ANIMATED_WEBP;\n  }\n\n  @Synthetic\n  Resource<Drawable> decode(@NonNull Source source, int width, int height, @NonNull Options options)\n      throws IOException {\n    Drawable decoded =\n        ImageDecoder.decodeDrawable(\n            source, new DefaultOnHeaderDecodedListener(width, height, options));\n    if (!(decoded instanceof AnimatedImageDrawable)) {\n      throw new IOException(\n          \"Received unexpected drawable type for animated webp, failing: \" + decoded);\n    }\n    return new AnimatedImageDrawableResource((AnimatedImageDrawable) decoded);\n  }\n\n  private static final class AnimatedImageDrawableResource implements Resource<Drawable> {\n    /** A totally made up number of the number of frames we think are held in memory at once... */\n    private static final int ESTIMATED_NUMBER_OF_FRAMES = 2;\n\n    private final AnimatedImageDrawable imageDrawable;\n\n    AnimatedImageDrawableResource(AnimatedImageDrawable imageDrawable) {\n      this.imageDrawable = imageDrawable;\n    }\n\n    @NonNull\n    @Override\n    public Class<Drawable> getResourceClass() {\n      return Drawable.class;\n    }\n\n    @NonNull\n    @Override\n    public AnimatedImageDrawable get() {\n      return imageDrawable;\n    }\n\n    @Override\n    public int getSize() {\n      return imageDrawable.getIntrinsicWidth()\n          * imageDrawable.getIntrinsicHeight()\n          * Util.getBytesPerPixel(Bitmap.Config.ARGB_8888)\n          * ESTIMATED_NUMBER_OF_FRAMES;\n    }\n\n    @Override\n    public void recycle() {\n      imageDrawable.stop();\n      imageDrawable.clearAnimationCallbacks();\n    }\n  }\n\n  private static final class StreamAnimatedWebpDecoder\n      implements ResourceDecoder<InputStream, Drawable> {\n\n    private final AnimatedWebpDecoder delegate;\n\n    StreamAnimatedWebpDecoder(AnimatedWebpDecoder delegate) {\n      this.delegate = delegate;\n    }\n\n    @Override\n    public boolean handles(@NonNull InputStream source, @NonNull Options options)\n        throws IOException {\n      return delegate.handles(source);\n    }\n\n    @Override\n    public Resource<Drawable> decode(\n        @NonNull InputStream is, int width, int height, @NonNull Options options)\n        throws IOException {\n      Source source = ImageDecoder.createSource(ByteBufferUtil.fromStream(is));\n      return delegate.decode(source, width, height, options);\n    }\n  }\n\n  private static final class ByteBufferAnimatedWebpDecoder\n      implements ResourceDecoder<ByteBuffer, Drawable> {\n\n    private final AnimatedWebpDecoder delegate;\n\n    ByteBufferAnimatedWebpDecoder(AnimatedWebpDecoder delegate) {\n      this.delegate = delegate;\n    }\n\n    @Override\n    public boolean handles(@NonNull ByteBuffer source, @NonNull Options options)\n        throws IOException {\n      return delegate.handles(source);\n    }\n\n    @Override\n    public Resource<Drawable> decode(\n        @NonNull ByteBuffer byteBuffer, int width, int height, @NonNull Options options)\n        throws IOException {\n      Source source = ImageDecoder.createSource(byteBuffer);\n      return delegate.decode(source, width, height, options);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/drawable/DrawableDecoderCompat.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.content.res.Resources.Theme;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build.VERSION;\nimport android.os.Build.VERSION_CODES;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.content.res.AppCompatResources;\nimport androidx.appcompat.view.ContextThemeWrapper;\nimport androidx.core.content.ContextCompat;\nimport androidx.core.content.res.ResourcesCompat;\n\n/**\n * Handles decoding Drawables with the v7 support library if present and falling back to the v4\n * support library otherwise.\n */\npublic final class DrawableDecoderCompat {\n  private static volatile boolean shouldCallAppCompatResources = true;\n\n  private DrawableDecoderCompat() {\n    // Utility class.\n  }\n\n  /** See {@code getDrawable(Context, int, Theme)}. */\n  public static Drawable getDrawable(\n      Context ourContext, Context targetContext, @DrawableRes int id) {\n    return getDrawable(ourContext, targetContext, id, /* theme= */ null);\n  }\n\n  /**\n   * Loads a Drawable using {@link AppCompatResources} if available and {@link ResourcesCompat}\n   * otherwise, depending on whether or not the v7 support library is included in the application.\n   *\n   * @param theme Used instead of the {@link Theme} returned from the given {@link Context} if\n   *     non-null when loading the {@link Drawable}.\n   */\n  public static Drawable getDrawable(\n      Context ourContext, @DrawableRes int id, @Nullable Theme theme) {\n    return getDrawable(ourContext, ourContext, id, theme);\n  }\n\n  private static Drawable getDrawable(\n      Context ourContext, Context targetContext, @DrawableRes int id, @Nullable Theme theme) {\n    try {\n      // Race conditions may cause us to attempt to load using v7 more than once. That's ok since\n      // this check is a modest optimization and the output will be correct anyway.\n      if (shouldCallAppCompatResources) {\n        return loadDrawableV7(targetContext, id, theme);\n      }\n    } catch (NoClassDefFoundError error) {\n      shouldCallAppCompatResources = false;\n    } catch (IllegalStateException e) {\n      if (ourContext.getPackageName().equals(targetContext.getPackageName())) {\n        throw e;\n      }\n      return ContextCompat.getDrawable(targetContext, id);\n    } catch (Resources.NotFoundException e) {\n      // Ignored, this can be thrown when drawable compat attempts to decode a canary resource. If\n      // that decode attempt fails, we still want to try with the v4 ResourcesCompat below.\n    }\n\n    return loadDrawableV4(targetContext, id, theme != null ? theme : targetContext.getTheme());\n  }\n\n  private static Drawable loadDrawableV7(\n      Context context, @DrawableRes int id, @Nullable Theme theme) {\n    Context resourceContext;\n    if (theme != null && VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {\n      ContextThemeWrapper contextThemeWrapper = new ContextThemeWrapper(context, theme);\n      contextThemeWrapper.applyOverrideConfiguration(theme.getResources().getConfiguration());\n      resourceContext = contextThemeWrapper;\n    } else {\n      resourceContext = context;\n    }\n    return AppCompatResources.getDrawable(resourceContext, id);\n  }\n\n  private static Drawable loadDrawableV4(\n      Context context, @DrawableRes int id, @Nullable Theme theme) {\n    return ResourcesCompat.getDrawable(context.getResources(), id, theme);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/drawable/DrawableResource.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.Drawable.ConstantState;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.engine.Initializable;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * Simple wrapper for an Android {@link Drawable} which returns a {@link\n * android.graphics.drawable.Drawable.ConstantState#newDrawable() new drawable} based on it's {@link\n * android.graphics.drawable.Drawable.ConstantState state}.\n *\n * <p><b>Suggested usages only include {@code T}s where the new drawable is of the same or\n * descendant class.</b>\n *\n * @param <T> type of the wrapped {@link Drawable}\n */\npublic abstract class DrawableResource<T extends Drawable> implements Resource<T>, Initializable {\n  protected final T drawable;\n\n  public DrawableResource(T drawable) {\n    this.drawable = Preconditions.checkNotNull(drawable);\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public final T get() {\n    @Nullable ConstantState state = drawable.getConstantState();\n    if (state == null) {\n      return drawable;\n    }\n    // Drawables contain temporary state related to how they're being displayed\n    // (alpha, color filter etc), so return a new copy each time.\n    // If we ever return the original drawable, it's temporary state may be changed\n    // and subsequent copies may end up with that temporary state. See #276.\n    return (T) state.newDrawable();\n  }\n\n  @Override\n  public void initialize() {\n    if (drawable instanceof BitmapDrawable) {\n      ((BitmapDrawable) drawable).getBitmap().prepareToDraw();\n    } else if (drawable instanceof GifDrawable) {\n      ((GifDrawable) drawable).getFirstFrame().prepareToDraw();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/drawable/DrawableTransitionOptions.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.TransitionOptions;\nimport com.bumptech.glide.request.transition.DrawableCrossFadeFactory;\nimport com.bumptech.glide.request.transition.TransitionFactory;\n\n/** Contains {@link Drawable} specific animation options. */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic final class DrawableTransitionOptions\n    extends TransitionOptions<DrawableTransitionOptions, Drawable> {\n\n  /**\n   * Returns a {@link DrawableTransitionOptions} object that enables a cross fade animation.\n   *\n   * @see #crossFade()\n   */\n  @NonNull\n  public static DrawableTransitionOptions withCrossFade() {\n    return new DrawableTransitionOptions().crossFade();\n  }\n\n  /**\n   * Returns a {@link DrawableTransitionOptions} object that enables a cross fade animation.\n   *\n   * @see #crossFade(int)\n   */\n  @NonNull\n  public static DrawableTransitionOptions withCrossFade(int duration) {\n    return new DrawableTransitionOptions().crossFade(duration);\n  }\n\n  /**\n   * Returns a {@link DrawableTransitionOptions} object that enables a cross fade animation.\n   *\n   * @see #crossFade(DrawableCrossFadeFactory)\n   */\n  @NonNull\n  public static DrawableTransitionOptions withCrossFade(\n      @NonNull DrawableCrossFadeFactory drawableCrossFadeFactory) {\n    return new DrawableTransitionOptions().crossFade(drawableCrossFadeFactory);\n  }\n\n  /**\n   * Returns a {@link DrawableTransitionOptions} object that enables a cross fade animation.\n   *\n   * @see #crossFade(DrawableCrossFadeFactory.Builder)\n   */\n  @NonNull\n  public static DrawableTransitionOptions withCrossFade(\n      @NonNull DrawableCrossFadeFactory.Builder builder) {\n    return new DrawableTransitionOptions().crossFade(builder);\n  }\n\n  /**\n   * Returns a {@link DrawableTransitionOptions} object that uses the given transition factory.\n   *\n   * @see com.bumptech.glide.GenericTransitionOptions#with(TransitionFactory)\n   */\n  @NonNull\n  public static DrawableTransitionOptions with(\n      @NonNull TransitionFactory<Drawable> transitionFactory) {\n    return new DrawableTransitionOptions().transition(transitionFactory);\n  }\n\n  /**\n   * Enables a cross fade animation between both the placeholder and the first resource and between\n   * subsequent resources (if thumbnails are used).\n   */\n  @NonNull\n  public DrawableTransitionOptions crossFade() {\n    return crossFade(new DrawableCrossFadeFactory.Builder());\n  }\n\n  /**\n   * Enables a cross fade animation between both the placeholder and the first resource and between\n   * subsequent resources (if thumbnails are used).\n   *\n   * @param duration The duration of the animation, see {@code\n   *     DrawableCrossFadeFactory.Builder(int)}\n   * @see com.bumptech.glide.request.transition.DrawableCrossFadeFactory.Builder\n   */\n  @NonNull\n  public DrawableTransitionOptions crossFade(int duration) {\n    return crossFade(new DrawableCrossFadeFactory.Builder(duration));\n  }\n\n  /**\n   * Enables a cross fade animation between both the placeholder and the first resource and between\n   * subsequent resources (if thumbnails are used).\n   */\n  @NonNull\n  public DrawableTransitionOptions crossFade(\n      @NonNull DrawableCrossFadeFactory drawableCrossFadeFactory) {\n    return transition(drawableCrossFadeFactory);\n  }\n\n  /**\n   * Enables a cross fade animation between both the placeholder and the first resource and between\n   * subsequent resources (if thumbnails are used).\n   */\n  @NonNull\n  public DrawableTransitionOptions crossFade(@NonNull DrawableCrossFadeFactory.Builder builder) {\n    return crossFade(builder.build());\n  }\n\n  // Make sure that we're not equal to any other concrete implementation of TransitionOptions.\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof DrawableTransitionOptions && super.equals(o);\n  }\n\n  // Our class doesn't include any additional properties, so we don't need to modify hashcode, but\n  // keep it here as a reminder in case we add properties.\n  @SuppressWarnings(\"PMD.UselessOverridingMethod\")\n  @Override\n  public int hashCode() {\n    return super.hashCode();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/drawable/NonOwnedDrawableResource.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.engine.Resource;\n\n/**\n * Handles generic {@link Drawable} types where we may be uncertain of their size or type and where\n * we don't know that it's safe for us to recycle or re-use the Drawable.\n */\nfinal class NonOwnedDrawableResource extends DrawableResource<Drawable> {\n\n  @SuppressWarnings(\"unchecked\")\n  @Nullable\n  static Resource<Drawable> newInstance(@Nullable Drawable drawable) {\n    return drawable != null ? new NonOwnedDrawableResource(drawable) : null;\n  }\n\n  private NonOwnedDrawableResource(Drawable drawable) {\n    super(drawable);\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public Class<Drawable> getResourceClass() {\n    return (Class<Drawable>) drawable.getClass();\n  }\n\n  @Override\n  public int getSize() {\n    // 4 bytes per pixel for ARGB_8888 Bitmaps is something of a reasonable approximation. If\n    // there are no intrinsic bounds, we can fall back just to 1.\n    return Math.max(1, drawable.getIntrinsicWidth() * drawable.getIntrinsicHeight() * 4);\n  }\n\n  @Override\n  public void recycle() {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/drawable/ResourceDrawableDecoder.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.content.res.Resources;\nimport android.content.res.Resources.Theme;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.text.TextUtils;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.List;\n\n/**\n * Decodes {@link Drawable}s given resource {@link Uri}s.\n *\n * <p>This is typically used as a fallback for resource types that either aren't Bitmaps (see #350)\n * or for resource types that we can't obtain an {@link java.io.InputStream} for using a standard\n * {@link ContentResolver}, including some types of application icons and resources loaded from\n * other packages.\n */\npublic class ResourceDrawableDecoder implements ResourceDecoder<Uri, Drawable> {\n\n  /** Specifies a {@link Theme} which will be used to load the drawable. */\n  public static final Option<Theme> THEME =\n      Option.memory(\"com.bumptech.glide.load.resource.bitmap.Downsampler.Theme\");\n\n  /**\n   * The package name to provide {@link Resources#getIdentifier(String, String, String)} when trying\n   * to find system resource ids.\n   *\n   * <p>As far as I can tell this is undocumented, but works.\n   */\n  private static final String ANDROID_PACKAGE_NAME = \"android\";\n\n  /**\n   * {@link Resources#getIdentifier(String, String, String)} documents that it will return 0 and\n   * that 0 is not a valid resouce id.\n   */\n  private static final int MISSING_RESOURCE_ID = 0;\n\n  // android.resource://<package_name>/<type>/<name>.\n  private static final int NAME_URI_PATH_SEGMENTS = 2;\n  private static final int TYPE_PATH_SEGMENT_INDEX = 0;\n  private static final int NAME_PATH_SEGMENT_INDEX = 1;\n  // android.resource://<package_name>/<resource_id>\n  private static final int ID_PATH_SEGMENTS = 1;\n  private static final int RESOURCE_ID_SEGMENT_INDEX = 0;\n\n  private final Context context;\n\n  public ResourceDrawableDecoder(Context context) {\n    this.context = context.getApplicationContext();\n  }\n\n  @Override\n  public boolean handles(@NonNull Uri source, @NonNull Options options) {\n    String scheme = source.getScheme();\n    return scheme != null && scheme.equals(ContentResolver.SCHEME_ANDROID_RESOURCE);\n  }\n\n  @Nullable\n  @Override\n  public Resource<Drawable> decode(\n      @NonNull Uri source, int width, int height, @NonNull Options options) {\n    String packageName = source.getAuthority();\n    if (TextUtils.isEmpty(packageName)) {\n      throw new IllegalStateException(\"Package name for \" + source + \" is null or empty\");\n    }\n    Context targetContext = findContextForPackage(source, packageName);\n    @DrawableRes int resId = findResourceIdFromUri(targetContext, source);\n    // Only use the provided theme if we're loading resources from our package. We can't get themes\n    // from other packages and we don't want to use a theme from our package when loading another\n    // package's resources.\n    Theme theme =\n        Preconditions.checkNotNull(packageName).equals(context.getPackageName())\n            ? options.get(THEME)\n            : null;\n    Drawable drawable =\n        theme == null\n            ? DrawableDecoderCompat.getDrawable(context, targetContext, resId)\n            : DrawableDecoderCompat.getDrawable(context, resId, theme);\n    return NonOwnedDrawableResource.newInstance(drawable);\n  }\n\n  @NonNull\n  private Context findContextForPackage(Uri source, @NonNull String packageName) {\n    // Fast path\n    if (packageName.equals(context.getPackageName())) {\n      return context;\n    }\n\n    try {\n      return context.createPackageContext(packageName, /* flags= */ 0);\n    } catch (NameNotFoundException e) {\n      // The parent APK holds the correct context if the resource is located in a split\n      if (packageName.contains(context.getPackageName())) {\n        return context;\n      }\n\n      throw new IllegalArgumentException(\n          \"Failed to obtain context or unrecognized Uri format for: \" + source, e);\n    }\n  }\n\n  @DrawableRes\n  private int findResourceIdFromUri(Context context, Uri source) {\n    List<String> segments = source.getPathSegments();\n    if (segments.size() == NAME_URI_PATH_SEGMENTS) {\n      return findResourceIdFromTypeAndNameResourceUri(context, source);\n    } else if (segments.size() == ID_PATH_SEGMENTS) {\n      return findResourceIdFromResourceIdUri(source);\n    } else {\n      throw new IllegalArgumentException(\"Unrecognized Uri format: \" + source);\n    }\n  }\n\n  // android.resource://com.android.camera2/mipmap/logo_camera_color\n  @DrawableRes\n  private int findResourceIdFromTypeAndNameResourceUri(Context context, Uri source) {\n    List<String> segments = source.getPathSegments();\n    String packageName = source.getAuthority();\n    String typeName = segments.get(TYPE_PATH_SEGMENT_INDEX);\n    String resourceName = segments.get(NAME_PATH_SEGMENT_INDEX);\n    int result = context.getResources().getIdentifier(resourceName, typeName, packageName);\n    if (result == MISSING_RESOURCE_ID) {\n      result = Resources.getSystem().getIdentifier(resourceName, typeName, ANDROID_PACKAGE_NAME);\n    }\n    if (result == MISSING_RESOURCE_ID) {\n      throw new IllegalArgumentException(\"Failed to find resource id for: \" + source);\n    }\n    return result;\n  }\n\n  // android.resource://com.android.camera2/123456\n  @DrawableRes\n  private int findResourceIdFromResourceIdUri(Uri source) {\n    List<String> segments = source.getPathSegments();\n    try {\n      return Integer.parseInt(segments.get(RESOURCE_ID_SEGMENT_INDEX));\n    } catch (NumberFormatException e) {\n      throw new IllegalArgumentException(\"Unrecognized Uri format: \" + source, e);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/drawable/UnitDrawableDecoder.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\n\n/** Passes through a {@link Drawable} as a {@link Drawable} based {@link Resource}. */\npublic class UnitDrawableDecoder implements ResourceDecoder<Drawable, Drawable> {\n  @Override\n  public boolean handles(@NonNull Drawable source, @NonNull Options options) {\n    return true;\n  }\n\n  @Nullable\n  @Override\n  public Resource<Drawable> decode(\n      @NonNull Drawable source, int width, int height, @NonNull Options options) {\n    return NonOwnedDrawableResource.newInstance(source);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/file/FileDecoder.java",
    "content": "package com.bumptech.glide.load.resource.file;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.io.File;\n\n/**\n * A simple {@link com.bumptech.glide.load.ResourceDecoder} that creates resource for a given {@link\n * java.io.File}.\n */\npublic class FileDecoder implements ResourceDecoder<File, File> {\n\n  @Override\n  public boolean handles(@NonNull File source, @NonNull Options options) {\n    return true;\n  }\n\n  @Override\n  public Resource<File> decode(\n      @NonNull File source, int width, int height, @NonNull Options options) {\n    return new FileResource(source);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/file/FileResource.java",
    "content": "package com.bumptech.glide.load.resource.file;\n\nimport com.bumptech.glide.load.resource.SimpleResource;\nimport java.io.File;\n\n/** A simple {@link com.bumptech.glide.load.engine.Resource} that wraps a {@link File}. */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic class FileResource extends SimpleResource<File> {\n  public FileResource(File file) {\n    super(file);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/ByteBufferGifDecoder.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.gifdecoder.GifHeader;\nimport com.bumptech.glide.gifdecoder.GifHeaderParser;\nimport com.bumptech.glide.gifdecoder.StandardGifDecoder;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.ImageHeaderParserUtils;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.UnitTransformation;\nimport com.bumptech.glide.util.LogTime;\nimport com.bumptech.glide.util.Util;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.List;\nimport java.util.Queue;\n\n/**\n * An {@link com.bumptech.glide.load.ResourceDecoder} that decodes {@link\n * com.bumptech.glide.load.resource.gif.GifDrawable} from {@link java.io.InputStream} data.\n */\npublic class ByteBufferGifDecoder implements ResourceDecoder<ByteBuffer, GifDrawable> {\n  private static final String TAG = \"BufferGifDecoder\";\n  private static final GifDecoderFactory GIF_DECODER_FACTORY = new GifDecoderFactory();\n  private static final GifHeaderParserPool PARSER_POOL = new GifHeaderParserPool();\n\n  private final Context context;\n  private final List<ImageHeaderParser> parsers;\n  private final GifHeaderParserPool parserPool;\n  private final GifDecoderFactory gifDecoderFactory;\n  private final GifBitmapProvider provider;\n\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public ByteBufferGifDecoder(Context context) {\n    this(\n        context,\n        Glide.get(context).getRegistry().getImageHeaderParsers(),\n        Glide.get(context).getBitmapPool(),\n        Glide.get(context).getArrayPool());\n  }\n\n  public ByteBufferGifDecoder(\n      Context context,\n      List<ImageHeaderParser> parsers,\n      BitmapPool bitmapPool,\n      ArrayPool arrayPool) {\n    this(context, parsers, bitmapPool, arrayPool, PARSER_POOL, GIF_DECODER_FACTORY);\n  }\n\n  @VisibleForTesting\n  ByteBufferGifDecoder(\n      Context context,\n      List<ImageHeaderParser> parsers,\n      BitmapPool bitmapPool,\n      ArrayPool arrayPool,\n      GifHeaderParserPool parserPool,\n      GifDecoderFactory gifDecoderFactory) {\n    this.context = context.getApplicationContext();\n    this.parsers = parsers;\n    this.gifDecoderFactory = gifDecoderFactory;\n    this.provider = new GifBitmapProvider(bitmapPool, arrayPool);\n    this.parserPool = parserPool;\n  }\n\n  @Override\n  public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) throws IOException {\n    return !options.get(GifOptions.DISABLE_ANIMATION)\n        && ImageHeaderParserUtils.getType(parsers, source) == ImageType.GIF;\n  }\n\n  @Override\n  public GifDrawableResource decode(\n      @NonNull ByteBuffer source, int width, int height, @NonNull Options options) {\n    final GifHeaderParser parser = parserPool.obtain(source);\n    try {\n      return decode(source, width, height, parser, options);\n    } finally {\n      parserPool.release(parser);\n    }\n  }\n\n  @Nullable\n  private GifDrawableResource decode(\n      ByteBuffer byteBuffer, int width, int height, GifHeaderParser parser, Options options) {\n    long startTime = LogTime.getLogTime();\n    try {\n      final GifHeader header = parser.parseHeader();\n      if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) {\n        // If we couldn't decode the GIF, we will end up with a frame count of 0.\n        return null;\n      }\n\n      Bitmap.Config config =\n          options.get(GifOptions.DECODE_FORMAT) == DecodeFormat.PREFER_RGB_565\n              ? Bitmap.Config.RGB_565\n              : Bitmap.Config.ARGB_8888;\n\n      int sampleSize = getSampleSize(header, width, height);\n      GifDecoder gifDecoder = gifDecoderFactory.build(provider, header, byteBuffer, sampleSize);\n      gifDecoder.setDefaultBitmapConfig(config);\n      gifDecoder.advance();\n      Bitmap firstFrame = gifDecoder.getNextFrame();\n      if (firstFrame == null) {\n        return null;\n      }\n\n      Transformation<Bitmap> unitTransformation = UnitTransformation.get();\n\n      GifDrawable gifDrawable =\n          new GifDrawable(context, gifDecoder, unitTransformation, width, height, firstFrame);\n\n      return new GifDrawableResource(gifDrawable);\n    } finally {\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Decoded GIF from stream in \" + LogTime.getElapsedMillis(startTime));\n      }\n    }\n  }\n\n  private static int getSampleSize(GifHeader gifHeader, int targetWidth, int targetHeight) {\n    int exactSampleSize =\n        Math.min(gifHeader.getHeight() / targetHeight, gifHeader.getWidth() / targetWidth);\n    int powerOfTwoSampleSize = exactSampleSize == 0 ? 0 : Integer.highestOneBit(exactSampleSize);\n    // Although functionally equivalent to 0 for BitmapFactory, 1 is a safer default for our code\n    // than 0.\n    int sampleSize = Math.max(1, powerOfTwoSampleSize);\n    if (Log.isLoggable(TAG, Log.VERBOSE) && sampleSize > 1) {\n      Log.v(\n          TAG,\n          \"Downsampling GIF\"\n              + \", sampleSize: \"\n              + sampleSize\n              + \", target dimens: [\"\n              + targetWidth\n              + \"x\"\n              + targetHeight\n              + \"]\"\n              + \", actual dimens: [\"\n              + gifHeader.getWidth()\n              + \"x\"\n              + gifHeader.getHeight()\n              + \"]\");\n    }\n    return sampleSize;\n  }\n\n  @VisibleForTesting\n  static class GifDecoderFactory {\n    GifDecoder build(\n        GifDecoder.BitmapProvider provider, GifHeader header, ByteBuffer data, int sampleSize) {\n      return new StandardGifDecoder(provider, header, data, sampleSize);\n    }\n  }\n\n  @VisibleForTesting\n  static class GifHeaderParserPool {\n    private final Queue<GifHeaderParser> pool = Util.createQueue(0);\n\n    synchronized GifHeaderParser obtain(ByteBuffer buffer) {\n      GifHeaderParser result = pool.poll();\n      if (result == null) {\n        result = new GifHeaderParser();\n      }\n      return result.setData(buffer);\n    }\n\n    synchronized void release(GifHeaderParser parser) {\n      parser.clear();\n      pool.offer(parser);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/GifBitmapProvider.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\n\n/**\n * Implements {@link com.bumptech.glide.gifdecoder.GifDecoder.BitmapProvider} by wrapping Glide's\n * {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}.\n */\npublic final class GifBitmapProvider implements GifDecoder.BitmapProvider {\n  private final BitmapPool bitmapPool;\n  @Nullable private final ArrayPool arrayPool;\n\n  /**\n   * Constructs an instance without a shared byte array pool. Byte arrays will be always constructed\n   * when requested.\n   */\n  public GifBitmapProvider(BitmapPool bitmapPool) {\n    this(bitmapPool, /* arrayPool= */ null);\n  }\n\n  /** Constructs an instance with a shared array pool. Arrays will be reused where possible. */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public GifBitmapProvider(BitmapPool bitmapPool, @Nullable ArrayPool arrayPool) {\n    this.bitmapPool = bitmapPool;\n    this.arrayPool = arrayPool;\n  }\n\n  @NonNull\n  @Override\n  public Bitmap obtain(int width, int height, @NonNull Bitmap.Config config) {\n    return bitmapPool.getDirty(width, height, config);\n  }\n\n  @Override\n  public void release(@NonNull Bitmap bitmap) {\n    bitmapPool.put(bitmap);\n  }\n\n  @NonNull\n  @Override\n  public byte[] obtainByteArray(int size) {\n    if (arrayPool == null) {\n      return new byte[size];\n    }\n    return arrayPool.get(size, byte[].class);\n  }\n\n  @Override\n  public void release(@NonNull byte[] bytes) {\n    if (arrayPool == null) {\n      return;\n    }\n    arrayPool.put(bytes);\n  }\n\n  @NonNull\n  @Override\n  public int[] obtainIntArray(int size) {\n    if (arrayPool == null) {\n      return new int[size];\n    }\n    return arrayPool.get(size, int[].class);\n  }\n\n  @SuppressWarnings(\"PMD.UseVarargs\")\n  @Override\n  public void release(@NonNull int[] array) {\n    if (arrayPool == null) {\n      return;\n    }\n    arrayPool.put(array);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.gifdecoder.GifDecoder.TOTAL_ITERATION_COUNT_FOREVER;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.Paint;\nimport android.graphics.PixelFormat;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Animatable;\nimport android.graphics.drawable.Drawable;\nimport android.view.Gravity;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport androidx.vectordrawable.graphics.drawable.Animatable2Compat;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Preconditions;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * An animated {@link android.graphics.drawable.Drawable} that plays the frames of an animated GIF.\n */\npublic class GifDrawable extends Drawable\n    implements GifFrameLoader.FrameCallback, Animatable, Animatable2Compat {\n  /** A constant indicating that an animated drawable should loop continuously. */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static final int LOOP_FOREVER = -1;\n\n  /**\n   * A constant indicating that an animated drawable should loop for its default number of times.\n   * For animated GIFs, this constant indicates the GIF should use the netscape loop count if\n   * present.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static final int LOOP_INTRINSIC = 0;\n\n  private static final int GRAVITY = Gravity.FILL;\n\n  private final GifState state;\n\n  /** True if the drawable is currently animating. */\n  private boolean isRunning;\n\n  /** True if the drawable should animate while visible. */\n  private boolean isStarted;\n\n  /** True if the drawable's resources have been recycled. */\n  private boolean isRecycled;\n\n  /**\n   * True if the drawable is currently visible. Default to true because on certain platforms (at\n   * least 4.1.1), setVisible is not called on {@link android.graphics.drawable.Drawable Drawables}\n   * during {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.\n   * See issue #130.\n   */\n  private boolean isVisible = true;\n\n  /** The number of times we've looped over all the frames in the GIF. */\n  private int loopCount;\n\n  /** The number of times to loop through the GIF animation. */\n  private int maxLoopCount = LOOP_FOREVER;\n\n  private boolean applyGravity;\n  private Paint paint;\n  private Rect destRect;\n\n  /** Callbacks to notify loop completion of a gif, where the loop count is explicitly specified. */\n  private List<AnimationCallback> animationCallbacks;\n\n  /**\n   * Constructor for GifDrawable.\n   *\n   * @param context A context.\n   * @param bitmapPool Ignored, see deprecation note.\n   * @param frameTransformation An {@link com.bumptech.glide.load.Transformation} that can be\n   *     applied to each frame.\n   * @param targetFrameWidth The desired width of the frames displayed by this drawable (the width\n   *     of the view or {@link com.bumptech.glide.request.target.Target} this drawable is being\n   *     loaded into).\n   * @param targetFrameHeight The desired height of the frames displayed by this drawable (the\n   *     height of the view or {@link com.bumptech.glide.request.target.Target} this drawable is\n   *     being loaded into).\n   * @param gifDecoder The decoder to use to decode GIF data.\n   * @param firstFrame The decoded and transformed first frame of this GIF.\n   * @see #setFrameTransformation(com.bumptech.glide.load.Transformation, android.graphics.Bitmap)\n   * @deprecated Use {@link #GifDrawable(Context, GifDecoder, Transformation, int, int, Bitmap)}\n   */\n  @SuppressWarnings(\"deprecation\")\n  @Deprecated\n  public GifDrawable(\n      Context context,\n      GifDecoder gifDecoder,\n      @SuppressWarnings(\"unused\") BitmapPool bitmapPool,\n      Transformation<Bitmap> frameTransformation,\n      int targetFrameWidth,\n      int targetFrameHeight,\n      Bitmap firstFrame) {\n    this(context, gifDecoder, frameTransformation, targetFrameWidth, targetFrameHeight, firstFrame);\n  }\n\n  /**\n   * Constructor for GifDrawable.\n   *\n   * @param context A context.\n   * @param frameTransformation An {@link com.bumptech.glide.load.Transformation} that can be\n   *     applied to each frame.\n   * @param targetFrameWidth The desired width of the frames displayed by this drawable (the width\n   *     of the view or {@link com.bumptech.glide.request.target.Target} this drawable is being\n   *     loaded into).\n   * @param targetFrameHeight The desired height of the frames displayed by this drawable (the\n   *     height of the view or {@link com.bumptech.glide.request.target.Target} this drawable is\n   *     being loaded into).\n   * @param gifDecoder The decoder to use to decode GIF data.\n   * @param firstFrame The decoded and transformed first frame of this GIF.\n   * @see #setFrameTransformation(com.bumptech.glide.load.Transformation, android.graphics.Bitmap)\n   */\n  public GifDrawable(\n      Context context,\n      GifDecoder gifDecoder,\n      Transformation<Bitmap> frameTransformation,\n      int targetFrameWidth,\n      int targetFrameHeight,\n      Bitmap firstFrame) {\n    this(\n        new GifState(\n            new GifFrameLoader(\n                // TODO(b/27524013): Factor out this call to Glide.get()\n                Glide.get(context),\n                gifDecoder,\n                targetFrameWidth,\n                targetFrameHeight,\n                frameTransformation,\n                firstFrame)));\n  }\n\n  GifDrawable(GifState state) {\n    this.state = Preconditions.checkNotNull(state);\n  }\n\n  @VisibleForTesting\n  GifDrawable(GifFrameLoader frameLoader, Paint paint) {\n    this(new GifState(frameLoader));\n    this.paint = paint;\n  }\n\n  public int getSize() {\n    return state.frameLoader.getSize();\n  }\n\n  public Bitmap getFirstFrame() {\n    return state.frameLoader.getFirstFrame();\n  }\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public void setFrameTransformation(\n      Transformation<Bitmap> frameTransformation, Bitmap firstFrame) {\n    state.frameLoader.setFrameTransformation(frameTransformation, firstFrame);\n  }\n\n  public Transformation<Bitmap> getFrameTransformation() {\n    return state.frameLoader.getFrameTransformation();\n  }\n\n  public ByteBuffer getBuffer() {\n    return state.frameLoader.getBuffer();\n  }\n\n  public int getFrameCount() {\n    return state.frameLoader.getFrameCount();\n  }\n\n  /**\n   * Returns the current frame index in the range 0..{@link #getFrameCount()} - 1, or -1 if no frame\n   * is displayed.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public int getFrameIndex() {\n    return state.frameLoader.getCurrentIndex();\n  }\n\n  private void resetLoopCount() {\n    loopCount = 0;\n  }\n\n  /**\n   * Starts the animation from the first frame. Can only be called while animation is not running.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public void startFromFirstFrame() {\n    Preconditions.checkArgument(!isRunning, \"You cannot restart a currently running animation.\");\n    state.frameLoader.setNextStartFromFirstFrame();\n    start();\n  }\n\n  @Override\n  public void start() {\n    isStarted = true;\n    resetLoopCount();\n    if (isVisible) {\n      startRunning();\n    }\n  }\n\n  @Override\n  public void stop() {\n    isStarted = false;\n    stopRunning();\n  }\n\n  private void startRunning() {\n    Preconditions.checkArgument(\n        !isRecycled,\n        \"You cannot start a recycled Drawable. Ensure that\"\n            + \"you clear any references to the Drawable when clearing the corresponding request.\");\n    // If we have only a single frame, we don't want to decode it endlessly.\n    if (state.frameLoader.getFrameCount() == 1) {\n      invalidateSelf();\n    } else if (!isRunning) {\n      isRunning = true;\n      state.frameLoader.subscribe(this);\n      invalidateSelf();\n    }\n  }\n\n  private void stopRunning() {\n    isRunning = false;\n    state.frameLoader.unsubscribe(this);\n  }\n\n  @Override\n  public boolean setVisible(boolean visible, boolean restart) {\n    Preconditions.checkArgument(\n        !isRecycled,\n        \"Cannot change the visibility of a recycled resource.\"\n            + \" Ensure that you unset the Drawable from your View before changing the View's\"\n            + \" visibility.\");\n    isVisible = visible;\n    if (!visible) {\n      stopRunning();\n    } else if (isStarted) {\n      startRunning();\n    }\n    return super.setVisible(visible, restart);\n  }\n\n  @Override\n  public int getIntrinsicWidth() {\n    return state.frameLoader.getWidth();\n  }\n\n  @Override\n  public int getIntrinsicHeight() {\n    return state.frameLoader.getHeight();\n  }\n\n  @Override\n  public boolean isRunning() {\n    return isRunning;\n  }\n\n  // For testing.\n  void setIsRunning(boolean isRunning) {\n    this.isRunning = isRunning;\n  }\n\n  @Override\n  protected void onBoundsChange(Rect bounds) {\n    super.onBoundsChange(bounds);\n    applyGravity = true;\n  }\n\n  @Override\n  public void draw(@NonNull Canvas canvas) {\n    if (isRecycled) {\n      return;\n    }\n\n    if (applyGravity) {\n      Gravity.apply(GRAVITY, getIntrinsicWidth(), getIntrinsicHeight(), getBounds(), getDestRect());\n      applyGravity = false;\n    }\n\n    Bitmap currentFrame = state.frameLoader.getCurrentFrame();\n    canvas.drawBitmap(currentFrame, null, getDestRect(), getPaint());\n  }\n\n  @Override\n  public void setAlpha(int i) {\n    getPaint().setAlpha(i);\n  }\n\n  @Override\n  public void setColorFilter(ColorFilter colorFilter) {\n    getPaint().setColorFilter(colorFilter);\n  }\n\n  private Rect getDestRect() {\n    if (destRect == null) {\n      destRect = new Rect();\n    }\n    return destRect;\n  }\n\n  private Paint getPaint() {\n    if (paint == null) {\n      paint = new Paint(Paint.FILTER_BITMAP_FLAG);\n    }\n    return paint;\n  }\n\n  @Override\n  public int getOpacity() {\n    // We can't tell, so default to transparent to be safe.\n    return PixelFormat.TRANSPARENT;\n  }\n\n  // See #1087.\n  private Callback findCallback() {\n    Callback callback = getCallback();\n    while (callback instanceof Drawable) {\n      callback = ((Drawable) callback).getCallback();\n    }\n    return callback;\n  }\n\n  @Override\n  public void onFrameReady() {\n    if (findCallback() == null) {\n      stop();\n      invalidateSelf();\n      return;\n    }\n\n    invalidateSelf();\n\n    if (getFrameIndex() == getFrameCount() - 1) {\n      loopCount++;\n    }\n\n    if (maxLoopCount != LOOP_FOREVER && loopCount >= maxLoopCount) {\n      stop();\n      notifyAnimationEndToListeners();\n    }\n  }\n\n  private void notifyAnimationEndToListeners() {\n    if (animationCallbacks != null) {\n      for (int i = 0, size = animationCallbacks.size(); i < size; i++) {\n        animationCallbacks.get(i).onAnimationEnd(this);\n      }\n    }\n  }\n\n  @Override\n  public ConstantState getConstantState() {\n    return state;\n  }\n\n  /** Clears any resources for loading frames that are currently held on to by this object. */\n  public void recycle() {\n    isRecycled = true;\n    state.frameLoader.clear();\n  }\n\n  // For testing.\n  boolean isRecycled() {\n    return isRecycled;\n  }\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public void setLoopCount(int loopCount) {\n    if (loopCount <= 0 && loopCount != LOOP_FOREVER && loopCount != LOOP_INTRINSIC) {\n      throw new IllegalArgumentException(\n          \"Loop count must be greater than 0, or equal to \"\n              + \"GlideDrawable.LOOP_FOREVER, or equal to GlideDrawable.LOOP_INTRINSIC\");\n    }\n\n    if (loopCount == LOOP_INTRINSIC) {\n      int intrinsicCount = state.frameLoader.getLoopCount();\n      maxLoopCount =\n          (intrinsicCount == TOTAL_ITERATION_COUNT_FOREVER) ? LOOP_FOREVER : intrinsicCount;\n    } else {\n      maxLoopCount = loopCount;\n    }\n  }\n\n  /**\n   * Register callback to listen to GifDrawable animation end event after specific loop count set by\n   * {@link GifDrawable#setLoopCount(int)}.\n   *\n   * <p>Note: This will only be called if the Gif stop because it reaches the loop count. Unregister\n   * this in onLoadCleared to avoid potential memory leak.\n   *\n   * @see GifDrawable#unregisterAnimationCallback(AnimationCallback).\n   * @param animationCallback Animation callback {@link Animatable2Compat.AnimationCallback}.\n   */\n  @Override\n  public void registerAnimationCallback(@NonNull AnimationCallback animationCallback) {\n    if (animationCallback == null) {\n      return;\n    }\n    if (animationCallbacks == null) {\n      animationCallbacks = new ArrayList<>();\n    }\n    animationCallbacks.add(animationCallback);\n  }\n\n  @Override\n  public boolean unregisterAnimationCallback(@NonNull AnimationCallback animationCallback) {\n    if (animationCallbacks == null || animationCallback == null) {\n      return false;\n    }\n    return animationCallbacks.remove(animationCallback);\n  }\n\n  @Override\n  public void clearAnimationCallbacks() {\n    if (animationCallbacks != null) {\n      animationCallbacks.clear();\n    }\n  }\n\n  static final class GifState extends ConstantState {\n    @VisibleForTesting final GifFrameLoader frameLoader;\n\n    GifState(GifFrameLoader frameLoader) {\n      this.frameLoader = frameLoader;\n    }\n\n    @NonNull\n    @Override\n    public Drawable newDrawable(Resources res) {\n      return newDrawable();\n    }\n\n    @NonNull\n    @Override\n    public Drawable newDrawable() {\n      return new GifDrawable(this);\n    }\n\n    @Override\n    public int getChangingConfigurations() {\n      return 0;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableEncoder.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.EncodeStrategy;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * Writes the original bytes of a {@link com.bumptech.glide.load.resource.gif.GifDrawable} to an\n * {@link java.io.OutputStream}.\n */\npublic class GifDrawableEncoder implements ResourceEncoder<GifDrawable> {\n  private static final String TAG = \"GifEncoder\";\n\n  @NonNull\n  @Override\n  public EncodeStrategy getEncodeStrategy(@NonNull Options options) {\n    return EncodeStrategy.SOURCE;\n  }\n\n  @Override\n  public boolean encode(\n      @NonNull Resource<GifDrawable> data, @NonNull File file, @NonNull Options options) {\n    GifDrawable drawable = data.get();\n    boolean success = false;\n    try {\n      ByteBufferUtil.toFile(drawable.getBuffer(), file);\n      success = true;\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Failed to encode GIF drawable data\", e);\n      }\n    }\n    return success;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.engine.Initializable;\nimport com.bumptech.glide.load.resource.drawable.DrawableResource;\n\n/** A resource wrapping an {@link com.bumptech.glide.load.resource.gif.GifDrawable}. */\npublic class GifDrawableResource extends DrawableResource<GifDrawable> implements Initializable {\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public GifDrawableResource(GifDrawable drawable) {\n    super(drawable);\n  }\n\n  @NonNull\n  @Override\n  public Class<GifDrawable> getResourceClass() {\n    return GifDrawable.class;\n  }\n\n  @Override\n  public int getSize() {\n    return drawable.getSize();\n  }\n\n  @Override\n  public void recycle() {\n    drawable.stop();\n    drawable.recycle();\n  }\n\n  @Override\n  public void initialize() {\n    drawable.getFirstFrame().prepareToDraw();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformation.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.bitmap.BitmapResource;\nimport com.bumptech.glide.util.Preconditions;\nimport java.security.MessageDigest;\n\n/**\n * An {@link com.bumptech.glide.load.Transformation} that wraps a transformation for a {@link\n * Bitmap} and can apply it to every frame of any {@link\n * com.bumptech.glide.load.resource.gif.GifDrawable}.\n */\npublic class GifDrawableTransformation implements Transformation<GifDrawable> {\n  private final Transformation<Bitmap> wrapped;\n\n  public GifDrawableTransformation(Transformation<Bitmap> wrapped) {\n    this.wrapped = Preconditions.checkNotNull(wrapped);\n  }\n\n  @NonNull\n  @Override\n  public Resource<GifDrawable> transform(\n      @NonNull Context context,\n      @NonNull Resource<GifDrawable> resource,\n      int outWidth,\n      int outHeight) {\n    GifDrawable drawable = resource.get();\n\n    // The drawable needs to be initialized with the correct width and height in order for a view\n    // displaying it to end up with the right dimensions. Since our transformations may arbitrarily\n    // modify the dimensions of our GIF, here we create a stand in for a frame and pass it to the\n    // transformation to see what the final transformed dimensions will be so that our drawable can\n    // report the correct intrinsic width and height.\n    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();\n    Bitmap firstFrame = drawable.getFirstFrame();\n    Resource<Bitmap> bitmapResource = new BitmapResource(firstFrame, bitmapPool);\n    Resource<Bitmap> transformed = wrapped.transform(context, bitmapResource, outWidth, outHeight);\n    if (!bitmapResource.equals(transformed)) {\n      bitmapResource.recycle();\n    }\n    Bitmap transformedFrame = transformed.get();\n\n    drawable.setFrameTransformation(wrapped, transformedFrame);\n    return resource;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof GifDrawableTransformation) {\n      GifDrawableTransformation other = (GifDrawableTransformation) o;\n      return wrapped.equals(other.wrapped);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return wrapped.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    wrapped.updateDiskCacheKey(messageDigest);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameLoader.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.request.RequestOptions.diskCacheStrategyOf;\nimport static com.bumptech.glide.request.RequestOptions.signatureOf;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.os.SystemClock;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.CustomTarget;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.List;\n\nclass GifFrameLoader {\n  private final GifDecoder gifDecoder;\n  private final Handler handler;\n  private final List<FrameCallback> callbacks = new ArrayList<>();\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  final RequestManager requestManager;\n\n  private final BitmapPool bitmapPool;\n\n  private boolean isRunning;\n  private boolean isLoadPending;\n  private boolean startFromFirstFrame;\n  private RequestBuilder<Bitmap> requestBuilder;\n  private DelayTarget current;\n  private boolean isCleared;\n  private DelayTarget next;\n  private Bitmap firstFrame;\n  private Transformation<Bitmap> transformation;\n  private DelayTarget pendingTarget;\n  @Nullable private GifFrameLoader.OnEveryFrameListener onEveryFrameListener;\n  private int firstFrameSize;\n  private int width;\n  private int height;\n\n  public interface FrameCallback {\n    void onFrameReady();\n  }\n\n  GifFrameLoader(\n      Glide glide,\n      GifDecoder gifDecoder,\n      int width,\n      int height,\n      Transformation<Bitmap> transformation,\n      Bitmap firstFrame) {\n    this(\n        glide.getBitmapPool(),\n        Glide.with(glide.getContext()),\n        gifDecoder,\n        null /*handler*/,\n        getRequestBuilder(Glide.with(glide.getContext()), width, height),\n        transformation,\n        firstFrame);\n  }\n\n  @SuppressWarnings(\"PMD.ConstructorCallsOverridableMethod\")\n  GifFrameLoader(\n      BitmapPool bitmapPool,\n      RequestManager requestManager,\n      GifDecoder gifDecoder,\n      Handler handler,\n      RequestBuilder<Bitmap> requestBuilder,\n      Transformation<Bitmap> transformation,\n      Bitmap firstFrame) {\n    this.requestManager = requestManager;\n    if (handler == null) {\n      handler = new Handler(Looper.getMainLooper(), new FrameLoaderCallback());\n    }\n    this.bitmapPool = bitmapPool;\n    this.handler = handler;\n    this.requestBuilder = requestBuilder;\n\n    this.gifDecoder = gifDecoder;\n\n    setFrameTransformation(transformation, firstFrame);\n  }\n\n  void setFrameTransformation(Transformation<Bitmap> transformation, Bitmap firstFrame) {\n    this.transformation = Preconditions.checkNotNull(transformation);\n    this.firstFrame = Preconditions.checkNotNull(firstFrame);\n    requestBuilder = requestBuilder.apply(new RequestOptions().transform(transformation));\n\n    firstFrameSize = Util.getBitmapByteSize(firstFrame);\n    width = firstFrame.getWidth();\n    height = firstFrame.getHeight();\n  }\n\n  Transformation<Bitmap> getFrameTransformation() {\n    return transformation;\n  }\n\n  Bitmap getFirstFrame() {\n    return firstFrame;\n  }\n\n  void subscribe(FrameCallback frameCallback) {\n    if (isCleared) {\n      throw new IllegalStateException(\"Cannot subscribe to a cleared frame loader\");\n    }\n    if (callbacks.contains(frameCallback)) {\n      throw new IllegalStateException(\"Cannot subscribe twice in a row\");\n    }\n    boolean start = callbacks.isEmpty();\n    callbacks.add(frameCallback);\n    if (start) {\n      start();\n    }\n  }\n\n  void unsubscribe(FrameCallback frameCallback) {\n    callbacks.remove(frameCallback);\n    if (callbacks.isEmpty()) {\n      stop();\n    }\n  }\n\n  int getWidth() {\n    return width;\n  }\n\n  int getHeight() {\n    return height;\n  }\n\n  int getSize() {\n    return gifDecoder.getByteSize() + firstFrameSize;\n  }\n\n  int getCurrentIndex() {\n    return current != null ? current.index : -1;\n  }\n\n  ByteBuffer getBuffer() {\n    return gifDecoder.getData().asReadOnlyBuffer();\n  }\n\n  int getFrameCount() {\n    return gifDecoder.getFrameCount();\n  }\n\n  int getLoopCount() {\n    return gifDecoder.getTotalIterationCount();\n  }\n\n  private void start() {\n    if (isRunning) {\n      return;\n    }\n    isRunning = true;\n    isCleared = false;\n\n    loadNextFrame();\n  }\n\n  private void stop() {\n    isRunning = false;\n  }\n\n  void clear() {\n    callbacks.clear();\n    recycleFirstFrame();\n    stop();\n    if (current != null) {\n      requestManager.clear(current);\n      current = null;\n    }\n    if (next != null) {\n      requestManager.clear(next);\n      next = null;\n    }\n    if (pendingTarget != null) {\n      requestManager.clear(pendingTarget);\n      pendingTarget = null;\n    }\n    gifDecoder.clear();\n    isCleared = true;\n  }\n\n  Bitmap getCurrentFrame() {\n    return current != null ? current.getResource() : firstFrame;\n  }\n\n  private void loadNextFrame() {\n    if (!isRunning || isLoadPending) {\n      return;\n    }\n    if (startFromFirstFrame) {\n      Preconditions.checkArgument(\n          pendingTarget == null, \"Pending target must be null when starting from the first frame\");\n      gifDecoder.resetFrameIndex();\n      startFromFirstFrame = false;\n    }\n    if (pendingTarget != null) {\n      DelayTarget temp = pendingTarget;\n      pendingTarget = null;\n      onFrameReady(temp);\n      return;\n    }\n    isLoadPending = true;\n    // Get the delay before incrementing the pointer because the delay indicates the amount of time\n    // we want to spend on the current frame.\n    int delay = gifDecoder.getNextDelay();\n    long targetTime = SystemClock.uptimeMillis() + delay;\n\n    gifDecoder.advance();\n    next = new DelayTarget(handler, gifDecoder.getCurrentFrameIndex(), targetTime);\n    requestBuilder.apply(signatureOf(getFrameSignature())).load(gifDecoder).into(next);\n  }\n\n  private void recycleFirstFrame() {\n    if (firstFrame != null) {\n      bitmapPool.put(firstFrame);\n      firstFrame = null;\n    }\n  }\n\n  void setNextStartFromFirstFrame() {\n    Preconditions.checkArgument(!isRunning, \"Can't restart a running animation\");\n    startFromFirstFrame = true;\n    if (pendingTarget != null) {\n      requestManager.clear(pendingTarget);\n      pendingTarget = null;\n    }\n  }\n\n  @VisibleForTesting\n  void setOnEveryFrameReadyListener(@Nullable OnEveryFrameListener onEveryFrameListener) {\n    this.onEveryFrameListener = onEveryFrameListener;\n  }\n\n  @VisibleForTesting\n  void onFrameReady(DelayTarget delayTarget) {\n    if (onEveryFrameListener != null) {\n      onEveryFrameListener.onFrameReady();\n    }\n    isLoadPending = false;\n    if (isCleared) {\n      handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, delayTarget).sendToTarget();\n      return;\n    }\n    // If we're not running, notifying here will recycle the frame that we might currently be\n    // showing, which breaks things (see #2526). We also can't discard this frame because we've\n    // already incremented the frame pointer and can't decode the same frame again. Instead we'll\n    // just hang on to this next frame until start() or clear() are called.\n    if (!isRunning) {\n      if (startFromFirstFrame) {\n        handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, delayTarget).sendToTarget();\n      } else {\n        pendingTarget = delayTarget;\n      }\n      return;\n    }\n\n    if (delayTarget.getResource() != null) {\n      recycleFirstFrame();\n      DelayTarget previous = current;\n      current = delayTarget;\n      // The callbacks may unregister when onFrameReady is called, so iterate in reverse to avoid\n      // concurrent modifications.\n      for (int i = callbacks.size() - 1; i >= 0; i--) {\n        FrameCallback cb = callbacks.get(i);\n        cb.onFrameReady();\n      }\n      if (previous != null) {\n        handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, previous).sendToTarget();\n      }\n    }\n\n    loadNextFrame();\n  }\n\n  private class FrameLoaderCallback implements Handler.Callback {\n    static final int MSG_DELAY = 1;\n    static final int MSG_CLEAR = 2;\n\n    @Synthetic\n    FrameLoaderCallback() {}\n\n    @Override\n    public boolean handleMessage(Message msg) {\n      if (msg.what == MSG_DELAY) {\n        GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj;\n        onFrameReady(target);\n        return true;\n      } else if (msg.what == MSG_CLEAR) {\n        GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj;\n        requestManager.clear(target);\n      }\n      return false;\n    }\n  }\n\n  @VisibleForTesting\n  static class DelayTarget extends CustomTarget<Bitmap> {\n    private final Handler handler;\n    @Synthetic final int index;\n    private final long targetTime;\n    private Bitmap resource;\n\n    DelayTarget(Handler handler, int index, long targetTime) {\n      this.handler = handler;\n      this.index = index;\n      this.targetTime = targetTime;\n    }\n\n    Bitmap getResource() {\n      return resource;\n    }\n\n    @Override\n    public void onResourceReady(\n        @NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {\n      this.resource = resource;\n      Message msg = handler.obtainMessage(FrameLoaderCallback.MSG_DELAY, this);\n      handler.sendMessageAtTime(msg, targetTime);\n    }\n\n    @Override\n    public void onLoadCleared(@Nullable Drawable placeholder) {\n      this.resource = null;\n    }\n  }\n\n  private static RequestBuilder<Bitmap> getRequestBuilder(\n      RequestManager requestManager, int width, int height) {\n    return requestManager\n        .asBitmap()\n        .apply(\n            diskCacheStrategyOf(DiskCacheStrategy.NONE)\n                .useAnimationPool(true)\n                .skipMemoryCache(true)\n                .override(width, height));\n  }\n\n  private static Key getFrameSignature() {\n    // Some devices seem to have crypto bugs that throw exceptions when you create a new UUID.\n    // See #1510.\n    return new ObjectKey(Math.random());\n  }\n\n  @VisibleForTesting\n  interface OnEveryFrameListener {\n    void onFrameReady();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameResourceDecoder.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.bitmap.BitmapResource;\n\n/**\n * Decodes {@link Bitmap}s from {@link GifDecoder}s representing a particular frame of a particular\n * GIF image.\n */\npublic final class GifFrameResourceDecoder implements ResourceDecoder<GifDecoder, Bitmap> {\n  private final BitmapPool bitmapPool;\n\n  public GifFrameResourceDecoder(BitmapPool bitmapPool) {\n    this.bitmapPool = bitmapPool;\n  }\n\n  @Override\n  public boolean handles(@NonNull GifDecoder source, @NonNull Options options) {\n    return true;\n  }\n\n  @Override\n  public Resource<Bitmap> decode(\n      @NonNull GifDecoder source, int width, int height, @NonNull Options options) {\n    Bitmap bitmap = source.getNextFrame();\n    return BitmapResource.obtain(bitmap, bitmapPool);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/GifOptions.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\n\n/** Options related to decoding GIFs. */\npublic final class GifOptions {\n\n  /**\n   * Indicates the {@link com.bumptech.glide.load.DecodeFormat} that will be used in conjunction\n   * with the particular GIF to determine the {@link android.graphics.Bitmap.Config} to use when\n   * decoding frames of GIFs.\n   */\n  public static final Option<DecodeFormat> DECODE_FORMAT =\n      Option.memory(\n          \"com.bumptech.glide.load.resource.gif.GifOptions.DecodeFormat\", DecodeFormat.DEFAULT);\n\n  /**\n   * If set to {@code true}, disables the GIF {@link com.bumptech.glide.load.ResourceDecoder}s\n   * ({@link ResourceDecoder#handles(Object, Options)} will return {@code false}). Defaults to\n   * {@code false}.\n   */\n  public static final Option<Boolean> DISABLE_ANIMATION =\n      Option.memory(\"com.bumptech.glide.load.resource.gif.GifOptions.DisableAnimation\", false);\n\n  private GifOptions() {\n    // Utility class.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/gif/StreamGifDecoder.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.ImageHeaderParserUtils;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.util.List;\n\n/**\n * A relatively inefficient decoder for {@link com.bumptech.glide.load.resource.gif.GifDrawable}\n * that converts {@link java.io.InputStream}s to {@link java.nio.ByteBuffer}s and then passes the\n * buffer to a wrapped decoder.\n */\npublic class StreamGifDecoder implements ResourceDecoder<InputStream, GifDrawable> {\n  private static final String TAG = \"StreamGifDecoder\";\n\n  private final List<ImageHeaderParser> parsers;\n  private final ResourceDecoder<ByteBuffer, GifDrawable> byteBufferDecoder;\n  private final ArrayPool byteArrayPool;\n\n  public StreamGifDecoder(\n      List<ImageHeaderParser> parsers,\n      ResourceDecoder<ByteBuffer, GifDrawable> byteBufferDecoder,\n      ArrayPool byteArrayPool) {\n    this.parsers = parsers;\n    this.byteBufferDecoder = byteBufferDecoder;\n    this.byteArrayPool = byteArrayPool;\n  }\n\n  @Override\n  public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {\n    return !options.get(GifOptions.DISABLE_ANIMATION)\n        && ImageHeaderParserUtils.getType(parsers, source, byteArrayPool) == ImageType.GIF;\n  }\n\n  @Override\n  public Resource<GifDrawable> decode(\n      @NonNull InputStream source, int width, int height, @NonNull Options options)\n      throws IOException {\n    byte[] data = inputStreamToBytes(source);\n    if (data == null) {\n      return null;\n    }\n    ByteBuffer byteBuffer = ByteBuffer.wrap(data);\n    return byteBufferDecoder.decode(byteBuffer, width, height, options);\n  }\n\n  private static byte[] inputStreamToBytes(InputStream is) {\n    final int bufferSize = 16384;\n    ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize);\n    try {\n      int nRead;\n      byte[] data = new byte[bufferSize];\n      while ((nRead = is.read(data)) != -1) {\n        buffer.write(data, 0, nRead);\n      }\n      buffer.flush();\n    } catch (IOException e) {\n      if (Log.isLoggable(TAG, Log.WARN)) {\n        Log.w(TAG, \"Error reading data from stream\", e);\n      }\n      return null;\n    }\n    return buffer.toByteArray();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapBytesTranscoder.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.resource.bytes.BytesResource;\nimport java.io.ByteArrayOutputStream;\n\n/**\n * An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that converts {@link\n * android.graphics.Bitmap}s into byte arrays using {@link android.graphics.Bitmap#compress\n * (android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream)}.\n */\npublic class BitmapBytesTranscoder implements ResourceTranscoder<Bitmap, byte[]> {\n  private final Bitmap.CompressFormat compressFormat;\n  private final int quality;\n\n  public BitmapBytesTranscoder() {\n    this(Bitmap.CompressFormat.JPEG, 100);\n  }\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public BitmapBytesTranscoder(@NonNull Bitmap.CompressFormat compressFormat, int quality) {\n    this.compressFormat = compressFormat;\n    this.quality = quality;\n  }\n\n  @Nullable\n  @Override\n  public Resource<byte[]> transcode(\n      @NonNull Resource<Bitmap> toTranscode, @NonNull Options options) {\n    ByteArrayOutputStream os = new ByteArrayOutputStream();\n    toTranscode.get().compress(compressFormat, quality, os);\n    toTranscode.recycle();\n    return new BytesResource(os.toByteArray());\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapDrawableTranscoder.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.bitmap.LazyBitmapDrawableResource;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that converts {@link\n * android.graphics.Bitmap}s into {@link android.graphics.drawable.BitmapDrawable}s.\n */\npublic class BitmapDrawableTranscoder implements ResourceTranscoder<Bitmap, BitmapDrawable> {\n  private final Resources resources;\n\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public BitmapDrawableTranscoder(@NonNull Context context) {\n    this(context.getResources());\n  }\n\n  /**\n   * @deprecated Use {@link #BitmapDrawableTranscoder(Resources)}, {@code bitmapPool} is unused.\n   */\n  @Deprecated\n  public BitmapDrawableTranscoder(\n      @NonNull Resources resources, @SuppressWarnings(\"unused\") BitmapPool bitmapPool) {\n    this(resources);\n  }\n\n  public BitmapDrawableTranscoder(@NonNull Resources resources) {\n    this.resources = Preconditions.checkNotNull(resources);\n  }\n\n  @Nullable\n  @Override\n  public Resource<BitmapDrawable> transcode(\n      @NonNull Resource<Bitmap> toTranscode, @NonNull Options options) {\n    return LazyBitmapDrawableResource.obtain(resources, toTranscode);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/transcode/DrawableBytesTranscoder.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.bitmap.BitmapResource;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\n\n/**\n * Obtains {@code byte[]} from {@link BitmapDrawable}s by delegating to a {@link ResourceTranscoder}\n * for {@link Bitmap}s to {@code byte[]}s.\n */\npublic final class DrawableBytesTranscoder implements ResourceTranscoder<Drawable, byte[]> {\n  private final BitmapPool bitmapPool;\n  private final ResourceTranscoder<Bitmap, byte[]> bitmapBytesTranscoder;\n  private final ResourceTranscoder<GifDrawable, byte[]> gifDrawableBytesTranscoder;\n\n  public DrawableBytesTranscoder(\n      @NonNull BitmapPool bitmapPool,\n      @NonNull ResourceTranscoder<Bitmap, byte[]> bitmapBytesTranscoder,\n      @NonNull ResourceTranscoder<GifDrawable, byte[]> gifDrawableBytesTranscoder) {\n    this.bitmapPool = bitmapPool;\n    this.bitmapBytesTranscoder = bitmapBytesTranscoder;\n    this.gifDrawableBytesTranscoder = gifDrawableBytesTranscoder;\n  }\n\n  @Nullable\n  @Override\n  public Resource<byte[]> transcode(\n      @NonNull Resource<Drawable> toTranscode, @NonNull Options options) {\n    Drawable drawable = toTranscode.get();\n    if (drawable instanceof BitmapDrawable) {\n      return bitmapBytesTranscoder.transcode(\n          BitmapResource.obtain(((BitmapDrawable) drawable).getBitmap(), bitmapPool), options);\n    } else if (drawable instanceof GifDrawable) {\n      return gifDrawableBytesTranscoder.transcode(toGifDrawableResource(toTranscode), options);\n    }\n    return null;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @NonNull\n  private static Resource<GifDrawable> toGifDrawableResource(@NonNull Resource<Drawable> resource) {\n    return (Resource<GifDrawable>) (Resource<?>) resource;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDrawableBytesTranscoder.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.resource.bytes.BytesResource;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.nio.ByteBuffer;\n\n/**\n * An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that converts {@link\n * com.bumptech.glide.load.resource.gif.GifDrawable} into bytes by obtaining the original bytes of\n * the GIF from the {@link com.bumptech.glide.load.resource.gif.GifDrawable}.\n */\npublic class GifDrawableBytesTranscoder implements ResourceTranscoder<GifDrawable, byte[]> {\n  @Nullable\n  @Override\n  public Resource<byte[]> transcode(\n      @NonNull Resource<GifDrawable> toTranscode, @NonNull Options options) {\n    GifDrawable gifData = toTranscode.get();\n    ByteBuffer byteBuffer = gifData.getBuffer();\n    return new BytesResource(ByteBufferUtil.toBytes(byteBuffer));\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/transcode/ResourceTranscoder.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\n\n/**\n * Transcodes a resource of one type to a resource of another type.\n *\n * @param <Z> The type of the resource that will be transcoded from.\n * @param <R> The type of the resource that will be transcoded to.\n */\npublic interface ResourceTranscoder<Z, R> {\n\n  /**\n   * Transcodes the given resource to the new resource type and returns the new resource.\n   *\n   * @param toTranscode The resource to transcode.\n   */\n  @Nullable\n  Resource<R> transcode(@NonNull Resource<Z> toTranscode, @NonNull Options options);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderRegistry.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A class that allows {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder}s to be\n * registered and retrieved by the classes they convert between.\n */\npublic class TranscoderRegistry {\n  private final List<Entry<?, ?>> transcoders = new ArrayList<>();\n\n  /**\n   * Registers the given {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} using\n   * the given classes so it can later be retrieved using the given classes.\n   *\n   * @param decodedClass The class of the resource that the transcoder transcodes from.\n   * @param transcodedClass The class of the resource that the transcoder transcodes to.\n   * @param transcoder The transcoder.\n   * @param <Z> The type of the resource that the transcoder transcodes from.\n   * @param <R> The type of the resource that the transcoder transcodes to.\n   */\n  public synchronized <Z, R> void register(\n      @NonNull Class<Z> decodedClass,\n      @NonNull Class<R> transcodedClass,\n      @NonNull ResourceTranscoder<Z, R> transcoder) {\n    transcoders.add(new Entry<>(decodedClass, transcodedClass, transcoder));\n  }\n\n  /**\n   * Returns the currently registered {@link\n   * com.bumptech.glide.load.resource.transcode.ResourceTranscoder} for the given classes.\n   *\n   * @param resourceClass The class of the resource that the transcoder transcodes from.\n   * @param transcodedClass The class of the resource that the transcoder transcodes to.\n   * @param <Z> The type of the resource that the transcoder transcodes from.\n   * @param <R> The type of the resource that the transcoder transcodes to.\n   */\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  public synchronized <Z, R> ResourceTranscoder<Z, R> get(\n      @NonNull Class<Z> resourceClass, @NonNull Class<R> transcodedClass) {\n    // For example, there may be a transcoder that can convert a GifDrawable to a Drawable, which\n    // will be caught above. However, if there is no registered transcoder, we can still just use\n    // the UnitTranscoder to return the Drawable because the transcode class (Drawable) is\n    // assignable from the resource class (GifDrawable).\n    if (transcodedClass.isAssignableFrom(resourceClass)) {\n      return (ResourceTranscoder<Z, R>) UnitTranscoder.get();\n    }\n    for (Entry<?, ?> entry : transcoders) {\n      if (entry.handles(resourceClass, transcodedClass)) {\n        return (ResourceTranscoder<Z, R>) entry.transcoder;\n      }\n    }\n\n    throw new IllegalArgumentException(\n        \"No transcoder registered to transcode from \" + resourceClass + \" to \" + transcodedClass);\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  public synchronized <Z, R> List<Class<R>> getTranscodeClasses(\n      @NonNull Class<Z> resourceClass, @NonNull Class<R> transcodeClass) {\n    List<Class<R>> transcodeClasses = new ArrayList<>();\n    // GifDrawable -> Drawable is just the UnitTranscoder, as is GifDrawable -> GifDrawable.\n    if (transcodeClass.isAssignableFrom(resourceClass)) {\n      transcodeClasses.add(transcodeClass);\n      return transcodeClasses;\n    }\n\n    for (Entry<?, ?> entry : transcoders) {\n      if (entry.handles(resourceClass, transcodeClass)\n          && !transcodeClasses.contains((Class<R>) entry.toClass)) {\n        transcodeClasses.add((Class<R>) entry.toClass);\n      }\n    }\n\n    return transcodeClasses;\n  }\n\n  private static final class Entry<Z, R> {\n    @Synthetic final Class<Z> fromClass;\n    @Synthetic final Class<R> toClass;\n    @Synthetic final ResourceTranscoder<Z, R> transcoder;\n\n    Entry(\n        @NonNull Class<Z> fromClass,\n        @NonNull Class<R> toClass,\n        @NonNull ResourceTranscoder<Z, R> transcoder) {\n      this.fromClass = fromClass;\n      this.toClass = toClass;\n      this.transcoder = transcoder;\n    }\n\n    /**\n     * If we convert from a specific Drawable, we must get that specific Drawable class or a\n     * subclass of that Drawable. In contrast, if we we convert <em>to</em> a specific Drawable, we\n     * can fulfill requests for a more generic parent class (like Drawable). As a result, we check\n     * fromClass and toClass in different orders.\n     */\n    public boolean handles(@NonNull Class<?> fromClass, @NonNull Class<?> toClass) {\n      return this.fromClass.isAssignableFrom(fromClass) && toClass.isAssignableFrom(this.toClass);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/load/resource/transcode/UnitTranscoder.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\n\n/**\n * A simple {@link ResourceTranscoder} that simply returns the given resource.\n *\n * @param <Z> The type of the resource that will be transcoded from and to.\n */\npublic class UnitTranscoder<Z> implements ResourceTranscoder<Z, Z> {\n  private static final UnitTranscoder<?> UNIT_TRANSCODER = new UnitTranscoder<>();\n\n  @SuppressWarnings(\"unchecked\")\n  public static <Z> ResourceTranscoder<Z, Z> get() {\n    return (ResourceTranscoder<Z, Z>) UNIT_TRANSCODER;\n  }\n\n  @Nullable\n  @Override\n  public Resource<Z> transcode(@NonNull Resource<Z> toTranscode, @NonNull Options options) {\n    return toTranscode;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/ApplicationLifecycle.java",
    "content": "package com.bumptech.glide.manager;\n\nimport androidx.annotation.NonNull;\n\n/**\n * A {@link com.bumptech.glide.manager.Lifecycle} implementation for tracking and notifying\n * listeners of {@link android.app.Application} lifecycle events.\n *\n * <p>Since there are essentially no {@link android.app.Application} lifecycle events, this class\n * simply defaults to notifying new listeners that they are started.\n */\nclass ApplicationLifecycle implements Lifecycle {\n  @Override\n  public void addListener(@NonNull LifecycleListener listener) {\n    listener.onStart();\n  }\n\n  @Override\n  public void removeListener(@NonNull LifecycleListener listener) {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitor.java",
    "content": "package com.bumptech.glide.manager;\n\n/** An interface for monitoring network connectivity events. */\npublic interface ConnectivityMonitor extends LifecycleListener {\n\n  /** An interface for listening to network connectivity events picked up by the monitor. */\n  interface ConnectivityListener {\n    /**\n     * Called when the connectivity state changes.\n     *\n     * @param isConnected True if we're currently connected to a network, false otherwise.\n     */\n    void onConnectivityChanged(boolean isConnected);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitorFactory.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\n\n/**\n * A factory class that produces a functional {@link\n * com.bumptech.glide.manager.ConnectivityMonitor}.\n */\npublic interface ConnectivityMonitorFactory {\n\n  @NonNull\n  ConnectivityMonitor build(\n      @NonNull Context context, @NonNull ConnectivityMonitor.ConnectivityListener listener);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/DefaultConnectivityMonitor.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.util.Synthetic;\n\n/**\n * An Android Lifecycle wrapper that uses {@link SingletonConnectivityReceiver} to observer\n * connectivity changes, allowing for registration to be removed when our listener is being\n * destroyed as part of the Android lifecycle.\n */\nfinal class DefaultConnectivityMonitor implements ConnectivityMonitor {\n  private final Context context;\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  final ConnectivityListener listener;\n\n  DefaultConnectivityMonitor(@NonNull Context context, @NonNull ConnectivityListener listener) {\n    this.context = context.getApplicationContext();\n    this.listener = listener;\n  }\n\n  private void register() {\n    SingletonConnectivityReceiver.get(context).register(listener);\n  }\n\n  private void unregister() {\n    SingletonConnectivityReceiver.get(context).unregister(listener);\n  }\n\n  @Override\n  public void onStart() {\n    register();\n  }\n\n  @Override\n  public void onStop() {\n    unregister();\n  }\n\n  @Override\n  public void onDestroy() {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/DefaultConnectivityMonitorFactory.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.core.content.ContextCompat;\n\n/**\n * A factory class that produces a functional {@link com.bumptech.glide.manager.ConnectivityMonitor}\n * if the application has the {@code android.permission.ACCESS_NETWORK_STATE} permission and a no-op\n * non functional {@link com.bumptech.glide.manager.ConnectivityMonitor} if the app does not have\n * the required permission.\n */\npublic class DefaultConnectivityMonitorFactory implements ConnectivityMonitorFactory {\n  private static final String TAG = \"ConnectivityMonitor\";\n  private static final String NETWORK_PERMISSION = \"android.permission.ACCESS_NETWORK_STATE\";\n\n  @NonNull\n  @Override\n  public ConnectivityMonitor build(\n      @NonNull Context context, @NonNull ConnectivityMonitor.ConnectivityListener listener) {\n    int permissionResult = ContextCompat.checkSelfPermission(context, NETWORK_PERMISSION);\n    boolean hasPermission = permissionResult == PackageManager.PERMISSION_GRANTED;\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      Log.d(\n          TAG,\n          hasPermission\n              ? \"ACCESS_NETWORK_STATE permission granted, registering connectivity monitor\"\n              : \"ACCESS_NETWORK_STATE permission missing, cannot register connectivity monitor\");\n    }\n    return hasPermission\n        ? new DefaultConnectivityMonitor(context, listener)\n        : new NullConnectivityMonitor();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/DoNothingFirstFrameWaiter.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.app.Activity;\n\nfinal class DoNothingFirstFrameWaiter implements FrameWaiter {\n\n  @Override\n  public void registerSelf(Activity activity) {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/EmptyRequestManagerTreeNode.java",
    "content": "package com.bumptech.glide.manager;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.RequestManager;\nimport java.util.Collections;\nimport java.util.Set;\n\n/** A {@link RequestManagerTreeNode} that returns no relatives. */\nfinal class EmptyRequestManagerTreeNode implements RequestManagerTreeNode {\n  @NonNull\n  @Override\n  public Set<RequestManager> getDescendants() {\n    return Collections.emptySet();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/FirstFrameWaiter.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.app.Activity;\nimport android.os.Build;\nimport android.view.View;\nimport android.view.ViewTreeObserver;\nimport android.view.ViewTreeObserver.OnDrawListener;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.load.resource.bitmap.HardwareConfigState;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.WeakHashMap;\n\n@RequiresApi(Build.VERSION_CODES.O)\nfinal class FirstFrameWaiter implements FrameWaiter {\n  @Synthetic\n  final Set<Activity> pendingActivities =\n      Collections.newSetFromMap(new WeakHashMap<Activity, Boolean>());\n\n  @Synthetic volatile boolean isFirstFrameSet;\n\n  @Override\n  public void registerSelf(Activity activity) {\n    // It's possible we'll create a few of these, but it's not particularly expensive to do so and\n    // we'd rather work around any edge cases that might prevent the first Activity we listen to\n    // from firing our callback ever.\n    if (isFirstFrameSet) {\n      return;\n    }\n    if (!pendingActivities.add(activity)) {\n      return;\n    }\n\n    final View view = activity.getWindow().getDecorView();\n    ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();\n    viewTreeObserver.addOnDrawListener(\n        new OnDrawListener() {\n          @Override\n          public void onDraw() {\n            // We can't remove the listener during onDraw, so always post the removal to the UI\n            // thread, even if the first frame may already be set before our listener goes off.\n            final OnDrawListener listener = this;\n            Util.postOnUiThread(\n                new Runnable() {\n                  @Override\n                  public void run() {\n                    HardwareConfigState.getInstance().unblockHardwareBitmaps();\n                    isFirstFrameSet = true;\n                    removeListener(view, listener);\n                    pendingActivities.clear();\n                  }\n                });\n          }\n        });\n  }\n\n  @Synthetic\n  static void removeListener(View view, OnDrawListener listener) {\n    // The original ViewTreeObserver might be merged into a new one and be dead.\n    // Since we have to handle that case anyway, We might as well always just\n    // obtain the current observer and use a single code path.\n    // We also have to wait to remove this because we're being called in onDraw.\n    ViewTreeObserver currentViewTreeObserver = view.getViewTreeObserver();\n    currentViewTreeObserver.removeOnDrawListener(listener);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/FrameWaiter.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.app.Activity;\n\ninterface FrameWaiter {\n  void registerSelf(Activity activity);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/Lifecycle.java",
    "content": "package com.bumptech.glide.manager;\n\nimport androidx.annotation.NonNull;\n\n/** An interface for listening to Activity/Fragment lifecycle events. */\npublic interface Lifecycle {\n  /** Adds the given listener to the set of listeners managed by this Lifecycle implementation. */\n  void addListener(@NonNull LifecycleListener listener);\n\n  /**\n   * Removes the given listener from the set of listeners managed by this Lifecycle implementation,\n   * returning {@code true} if the listener was removed successfully, and {@code false} otherwise.\n   *\n   * <p>This is an optimization only, there is no guarantee that every added listener will\n   * eventually be removed.\n   */\n  void removeListener(@NonNull LifecycleListener listener);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/LifecycleLifecycle.java",
    "content": "package com.bumptech.glide.manager;\n\nimport androidx.annotation.NonNull;\nimport androidx.lifecycle.Lifecycle.Event;\nimport androidx.lifecycle.Lifecycle.State;\nimport androidx.lifecycle.LifecycleObserver;\nimport androidx.lifecycle.LifecycleOwner;\nimport androidx.lifecycle.OnLifecycleEvent;\nimport com.bumptech.glide.util.Util;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@SuppressWarnings(\"OnLifecycleEvent\") // Glide doesn't support Java 8\nfinal class LifecycleLifecycle implements Lifecycle, LifecycleObserver {\n  @NonNull\n  private final Set<LifecycleListener> lifecycleListeners = new HashSet<LifecycleListener>();\n\n  @NonNull private final androidx.lifecycle.Lifecycle lifecycle;\n\n  LifecycleLifecycle(androidx.lifecycle.Lifecycle lifecycle) {\n    this.lifecycle = lifecycle;\n    lifecycle.addObserver(this);\n  }\n\n  @OnLifecycleEvent(Event.ON_START)\n  public void onStart(@NonNull LifecycleOwner owner) {\n    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {\n      lifecycleListener.onStart();\n    }\n  }\n\n  @OnLifecycleEvent(Event.ON_STOP)\n  public void onStop(@NonNull LifecycleOwner owner) {\n    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {\n      lifecycleListener.onStop();\n    }\n  }\n\n  @OnLifecycleEvent(Event.ON_DESTROY)\n  public void onDestroy(@NonNull LifecycleOwner owner) {\n    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {\n      lifecycleListener.onDestroy();\n    }\n    owner.getLifecycle().removeObserver(this);\n  }\n\n  @Override\n  public void addListener(@NonNull LifecycleListener listener) {\n    lifecycleListeners.add(listener);\n\n    if (lifecycle.getCurrentState() == State.DESTROYED) {\n      listener.onDestroy();\n    } else if (lifecycle.getCurrentState().isAtLeast(State.STARTED)) {\n      listener.onStart();\n    } else {\n      listener.onStop();\n    }\n  }\n\n  @Override\n  public void removeListener(@NonNull LifecycleListener listener) {\n    lifecycleListeners.remove(listener);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/LifecycleListener.java",
    "content": "package com.bumptech.glide.manager;\n\n/**\n * An interface for listener to {@link android.app.Fragment} and {@link android.app.Activity}\n * lifecycle events.\n */\npublic interface LifecycleListener {\n\n  /**\n   * Callback for when {@link android.app.Fragment#onStart()}} or {@link\n   * android.app.Activity#onStart()} is called.\n   */\n  void onStart();\n\n  /**\n   * Callback for when {@link android.app.Fragment#onStop()}} or {@link\n   * android.app.Activity#onStop()}} is called.\n   */\n  void onStop();\n\n  /**\n   * Callback for when {@link android.app.Fragment#onDestroy()}} or {@link\n   * android.app.Activity#onDestroy()} is called.\n   */\n  void onDestroy();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/LifecycleRequestManagerRetriever.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.lifecycle.Lifecycle;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nfinal class LifecycleRequestManagerRetriever {\n  @Synthetic final Map<Lifecycle, RequestManager> lifecycleToRequestManager = new HashMap<>();\n  @NonNull private final RequestManagerFactory factory;\n\n  LifecycleRequestManagerRetriever(@NonNull RequestManagerFactory factory) {\n    this.factory = factory;\n  }\n\n  RequestManager getOnly(Lifecycle lifecycle) {\n    Util.assertMainThread();\n    return lifecycleToRequestManager.get(lifecycle);\n  }\n\n  RequestManager getOrCreate(\n      Context context,\n      Glide glide,\n      final Lifecycle lifecycle,\n      FragmentManager childFragmentManager,\n      boolean isParentVisible) {\n    Util.assertMainThread();\n    RequestManager result = getOnly(lifecycle);\n    if (result == null) {\n      LifecycleLifecycle glideLifecycle = new LifecycleLifecycle(lifecycle);\n      result =\n          factory.build(\n              glide,\n              glideLifecycle,\n              new SupportRequestManagerTreeNode(childFragmentManager),\n              context);\n      lifecycleToRequestManager.put(lifecycle, result);\n      glideLifecycle.addListener(\n          new LifecycleListener() {\n            @Override\n            public void onStart() {}\n\n            @Override\n            public void onStop() {}\n\n            @Override\n            public void onDestroy() {\n              lifecycleToRequestManager.remove(lifecycle);\n            }\n          });\n      // This is a bit of hack, we're going to start the RequestManager, but not the\n      // corresponding Lifecycle. It's safe to start the RequestManager, but starting the\n      // Lifecycle might trigger memory leaks. See b/154405040\n      if (isParentVisible) {\n        result.onStart();\n      }\n    }\n    return result;\n  }\n\n  private final class SupportRequestManagerTreeNode implements RequestManagerTreeNode {\n    private final FragmentManager childFragmentManager;\n\n    SupportRequestManagerTreeNode(FragmentManager childFragmentManager) {\n      this.childFragmentManager = childFragmentManager;\n    }\n\n    @NonNull\n    @Override\n    public Set<RequestManager> getDescendants() {\n      Set<RequestManager> result = new HashSet<>();\n      getChildFragmentsRecursive(childFragmentManager, result);\n      return result;\n    }\n\n    private void getChildFragmentsRecursive(\n        FragmentManager fragmentManager, Set<RequestManager> requestManagers) {\n      List<Fragment> children = fragmentManager.getFragments();\n      for (int i = 0, size = children.size(); i < size; i++) {\n        Fragment child = children.get(i);\n        getChildFragmentsRecursive(child.getChildFragmentManager(), requestManagers);\n        RequestManager fromChild = getOnly(child.getLifecycle());\n        if (fromChild != null) {\n          requestManagers.add(fromChild);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/NullConnectivityMonitor.java",
    "content": "package com.bumptech.glide.manager;\n\n/** A no-op {@link com.bumptech.glide.manager.ConnectivityMonitor}. */\nclass NullConnectivityMonitor implements ConnectivityMonitor {\n\n  @Override\n  public void onStart() {\n    // Do nothing.\n  }\n\n  @Override\n  public void onStop() {\n    // Do nothing.\n  }\n\n  @Override\n  public void onDestroy() {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.app.Fragment;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.RequestManager;\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * @deprecated This class is unused by Glide and contains only no-op methods. It's retained along\n *     with its public methods to avoid breaking binary compatibility. Lifecycle integration is no\n *     longer supported outside of androidx Activitys and Fragments.\n */\n@Deprecated\npublic class RequestManagerFragment extends Fragment {\n  /**\n   * @deprecated This method is a no-op. See the class comment for deprecation details.\n   */\n  @Deprecated\n  public void setRequestManager(@Nullable RequestManager requestManager) {}\n\n  /**\n   * @deprecated This always returns null. See the class comment for deprecation details.\n   */\n  @Deprecated\n  @Nullable\n  public RequestManager getRequestManager() {\n    return null;\n  }\n\n  /**\n   * @deprecated This always returns an empty tree node. See the class comment for deprecation\n   *     details.\n   */\n  @Deprecated\n  @NonNull\n  public RequestManagerTreeNode getRequestManagerTreeNode() {\n    return new RequestManagerTreeNode() {\n      @NonNull\n      @Override\n      public Set<RequestManager> getDescendants() {\n        return Collections.emptySet();\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/RequestManagerRetriever.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.Application;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.view.View;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport androidx.collection.ArrayMap;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.fragment.app.FragmentManager;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.load.resource.bitmap.HardwareConfigState;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Util;\nimport java.util.Collection;\nimport java.util.Map;\n\n/**\n * A collection of static methods for creating new {@link com.bumptech.glide.RequestManager}s or\n * retrieving existing ones from activities and fragment.\n */\npublic class RequestManagerRetriever implements Handler.Callback {\n  @VisibleForTesting static final String FRAGMENT_TAG = \"com.bumptech.glide.manager\";\n\n  /** The top application level RequestManager. */\n  private volatile RequestManager applicationManager;\n\n  private final RequestManagerFactory factory;\n\n  // Objects used to find Fragments and Activities containing views.\n  private final ArrayMap<View, Fragment> tempViewToSupportFragment = new ArrayMap<>();\n  // This is really misplaced here, but to put it anywhere else means duplicating all of the\n  // Fragment/Activity extraction logic that already exists here. It's gross, but less likely to\n  // break.\n  private final FrameWaiter frameWaiter;\n  private final LifecycleRequestManagerRetriever lifecycleRequestManagerRetriever;\n\n  public RequestManagerRetriever(@Nullable RequestManagerFactory factory) {\n    this.factory = factory != null ? factory : DEFAULT_FACTORY;\n    lifecycleRequestManagerRetriever = new LifecycleRequestManagerRetriever(this.factory);\n    frameWaiter = buildFrameWaiter();\n  }\n\n  private static FrameWaiter buildFrameWaiter() {\n    if (!HardwareConfigState.HARDWARE_BITMAPS_SUPPORTED\n        || !HardwareConfigState.BLOCK_HARDWARE_BITMAPS_WHEN_GL_CONTEXT_MIGHT_NOT_BE_INITIALIZED) {\n      return new DoNothingFirstFrameWaiter();\n    }\n    return new FirstFrameWaiter();\n  }\n\n  @NonNull\n  private RequestManager getApplicationManager(@NonNull Context context) {\n    // Either an application context or we're on a background thread.\n    if (applicationManager == null) {\n      synchronized (this) {\n        if (applicationManager == null) {\n          // Normally pause/resume is taken care of by the fragment we add to the fragment or\n          // activity. However, in this case since the manager attached to the application will not\n          // receive lifecycle events, we must force the manager to start resumed using\n          // ApplicationLifecycle.\n\n          // TODO(b/27524013): Factor out this Glide.get() call.\n          Glide glide = Glide.get(context.getApplicationContext());\n          applicationManager =\n              factory.build(\n                  glide,\n                  new ApplicationLifecycle(),\n                  new EmptyRequestManagerTreeNode(),\n                  context.getApplicationContext());\n        }\n      }\n    }\n\n    return applicationManager;\n  }\n\n  @NonNull\n  public RequestManager get(@NonNull Context context) {\n    if (context == null) {\n      throw new IllegalArgumentException(\"You cannot start a load on a null Context\");\n    } else if (Util.isOnMainThread() && !(context instanceof Application)) {\n      if (context instanceof FragmentActivity) {\n        return get((FragmentActivity) context);\n      } else if (context instanceof ContextWrapper\n          // Only unwrap a ContextWrapper if the baseContext has a non-null application context.\n          // Context#createPackageContext may return a Context without an Application instance,\n          // in which case a ContextWrapper may be used to attach one.\n          && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {\n        return get(((ContextWrapper) context).getBaseContext());\n      }\n    }\n\n    return getApplicationManager(context);\n  }\n\n  @NonNull\n  public RequestManager get(@NonNull FragmentActivity activity) {\n    if (Util.isOnBackgroundThread()) {\n      return get(activity.getApplicationContext());\n    }\n    assertNotDestroyed(activity);\n    frameWaiter.registerSelf(activity);\n    boolean isActivityVisible = isActivityVisible(activity);\n    Glide glide = Glide.get(activity.getApplicationContext());\n    return lifecycleRequestManagerRetriever.getOrCreate(\n        activity,\n        glide,\n        activity.getLifecycle(),\n        activity.getSupportFragmentManager(),\n        isActivityVisible);\n  }\n\n  @NonNull\n  public RequestManager get(@NonNull Fragment fragment) {\n    Preconditions.checkNotNull(\n        fragment.getContext(),\n        \"You cannot start a load on a fragment before it is attached or after it is destroyed\");\n    if (Util.isOnBackgroundThread()) {\n      return get(fragment.getContext().getApplicationContext());\n    }\n    // In some unusual cases, it's possible to have a Fragment not hosted by an activity. There's\n    // not all that much we can do here. Most apps will be started with a standard activity. If\n    // we manage not to register the first frame waiter for a while, the consequences are not\n    // catastrophic, we'll just use some extra memory.\n    if (fragment.getActivity() != null) {\n      frameWaiter.registerSelf(fragment.getActivity());\n    }\n    FragmentManager fm = fragment.getChildFragmentManager();\n    Context context = fragment.getContext();\n    Glide glide = Glide.get(context.getApplicationContext());\n    return lifecycleRequestManagerRetriever.getOrCreate(\n        context, glide, fragment.getLifecycle(), fm, fragment.isVisible());\n  }\n\n  /**\n   * @deprecated This is identical to calling {@link #get(Context)} with the application context.\n   *     Use androidx Activities instead (ie {@link FragmentActivity}, or {@link\n   *     androidx.appcompat.app.AppCompatActivity}).\n   */\n  @Deprecated\n  @NonNull\n  public RequestManager get(@NonNull Activity activity) {\n    return get(activity.getApplicationContext());\n  }\n\n  @NonNull\n  public RequestManager get(@NonNull View view) {\n    if (Util.isOnBackgroundThread()) {\n      return get(view.getContext().getApplicationContext());\n    }\n\n    Preconditions.checkNotNull(view);\n    Preconditions.checkNotNull(\n        view.getContext(), \"Unable to obtain a request manager for a view without a Context\");\n    Activity activity = findActivity(view.getContext());\n    // The view might be somewhere else, like a service.\n    if (activity == null) {\n      return get(view.getContext().getApplicationContext());\n    }\n\n    // Support Fragments.\n    // Although the user might have non-support Fragments attached to FragmentActivity, searching\n    // for non-support Fragments is so expensive pre O and that should be rare enough that we\n    // prefer to just fall back to the Activity directly.\n    if (activity instanceof FragmentActivity) {\n      Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);\n      return fragment != null ? get(fragment) : get((FragmentActivity) activity);\n    }\n\n    // Standard Fragments.\n    return get(view.getContext().getApplicationContext());\n  }\n\n  private static void findAllSupportFragmentsWithViews(\n      @Nullable Collection<Fragment> topLevelFragments, @NonNull Map<View, Fragment> result) {\n    if (topLevelFragments == null) {\n      return;\n    }\n    for (Fragment fragment : topLevelFragments) {\n      // getFragment()s in the support FragmentManager may contain null values, see #1991.\n      if (fragment == null || fragment.getView() == null) {\n        continue;\n      }\n      result.put(fragment.getView(), fragment);\n      findAllSupportFragmentsWithViews(fragment.getChildFragmentManager().getFragments(), result);\n    }\n  }\n\n  @Nullable\n  private Fragment findSupportFragment(@NonNull View target, @NonNull FragmentActivity activity) {\n    tempViewToSupportFragment.clear();\n    findAllSupportFragmentsWithViews(\n        activity.getSupportFragmentManager().getFragments(), tempViewToSupportFragment);\n    Fragment result = null;\n    View activityRoot = activity.findViewById(android.R.id.content);\n    View current = target;\n    while (!current.equals(activityRoot)) {\n      result = tempViewToSupportFragment.get(current);\n      if (result != null) {\n        break;\n      }\n      if (current.getParent() instanceof View) {\n        current = (View) current.getParent();\n      } else {\n        break;\n      }\n    }\n\n    tempViewToSupportFragment.clear();\n    return result;\n  }\n\n  @Nullable\n  private static Activity findActivity(@NonNull Context context) {\n    if (context instanceof Activity) {\n      return (Activity) context;\n    } else if (context instanceof ContextWrapper) {\n      return findActivity(((ContextWrapper) context).getBaseContext());\n    } else {\n      return null;\n    }\n  }\n\n  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n  private static void assertNotDestroyed(@NonNull Activity activity) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {\n      throw new IllegalArgumentException(\"You cannot start a load for a destroyed activity\");\n    }\n  }\n\n  /**\n   * @deprecated This is equivalent to calling {@link #get(Context)} with the application context.\n   *     Use androidx fragments instead: {@link Fragment}.\n   */\n  @Deprecated\n  @NonNull\n  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n  public RequestManager get(@NonNull android.app.Fragment fragment) {\n    if (fragment.getActivity() == null) {\n      throw new IllegalArgumentException(\n          \"You cannot start a load on a fragment before it is attached\");\n    }\n    return get(fragment.getActivity().getApplicationContext());\n  }\n\n  private static boolean isActivityVisible(Context context) {\n    // This is a poor heuristic, but it's about all we have. We'd rather err on the side of visible\n    // and start requests than on the side of invisible and ignore valid requests.\n    Activity activity = findActivity(context);\n    return activity == null || !activity.isFinishing();\n  }\n\n  /**\n   * @deprecated This method is no longer called by Glide or provides any functionality and it will\n   *     be removed in the future. Retained for now to preserve backwards compatibility.\n   */\n  @Deprecated\n  @SuppressWarnings(\"PMD.CollapsibleIfStatements\")\n  @Override\n  public boolean handleMessage(Message message) {\n    return false;\n  }\n\n  /** Used internally to create {@link RequestManager}s. */\n  public interface RequestManagerFactory {\n    @NonNull\n    RequestManager build(\n        @NonNull Glide glide,\n        @NonNull Lifecycle lifecycle,\n        @NonNull RequestManagerTreeNode requestManagerTreeNode,\n        @NonNull Context context);\n  }\n\n  private static final RequestManagerFactory DEFAULT_FACTORY =\n      new RequestManagerFactory() {\n        @NonNull\n        @Override\n        public RequestManager build(\n            @NonNull Glide glide,\n            @NonNull Lifecycle lifecycle,\n            @NonNull RequestManagerTreeNode requestManagerTreeNode,\n            @NonNull Context context) {\n          return new RequestManager(glide, lifecycle, requestManagerTreeNode, context);\n        }\n      };\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/RequestManagerTreeNode.java",
    "content": "package com.bumptech.glide.manager;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.RequestManager;\nimport java.util.Set;\n\n/**\n * Provides access to the relatives of a RequestManager based on the current context. The context\n * hierarchy is provided by nesting in Activity and Fragments; the application context does not\n * provide access to any other RequestManagers hierarchically.\n */\npublic interface RequestManagerTreeNode {\n  /**\n   * Returns all descendant {@link RequestManager}s relative to the context of the current {@link\n   * RequestManager}.\n   */\n  @NonNull\n  Set<RequestManager> getDescendants();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/RequestTracker.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.util.Util;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.WeakHashMap;\n\n/**\n * A class for tracking, canceling, and restarting in progress, completed, and failed requests.\n *\n * <p>This class is not thread safe and must be accessed on the main thread.\n */\npublic class RequestTracker {\n  private static final String TAG = \"RequestTracker\";\n  // Most requests will be for views and will therefore be held strongly (and safely) by the view\n  // via the tag. However, a user can always pass in a different type of target which may end up not\n  // being strongly referenced even though the user still would like the request to finish. Weak\n  // references are therefore only really functional in this context for view targets. Despite the\n  // side affects, WeakReferences are still essentially required. A user can always make repeated\n  // requests into targets other than views, or use an activity manager in a fragment pager where\n  // holding strong references would steadily leak bitmaps and/or views.\n  private final Set<Request> requests =\n      Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());\n  // A set of requests that have not completed and are queued to be run again. We use this list to\n  // maintain hard references to these requests to ensure that they are not garbage collected\n  // before they start running or while they are paused. See #346.\n  private final Set<Request> pendingRequests = new HashSet<>();\n\n  private boolean isPaused;\n\n  /** Starts tracking the given request. */\n  public void runRequest(@NonNull Request request) {\n    requests.add(request);\n    if (!isPaused) {\n      request.begin();\n    } else {\n      request.clear();\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Paused, delaying request\");\n      }\n      pendingRequests.add(request);\n    }\n  }\n\n  @VisibleForTesting\n  void addRequest(Request request) {\n    requests.add(request);\n  }\n\n  /**\n   * Stops tracking the given request, clears, and recycles it, and returns {@code true} if the\n   * request was removed or invalid or {@code false} if the request was not found.\n   */\n  public boolean clearAndRemove(@Nullable Request request) {\n    if (request == null) {\n      // If the Request is null, the request is already cleared and we don't need to search further\n      // for its owner.\n      return true;\n    }\n    boolean isOwnedByUs = requests.remove(request);\n    // Avoid short circuiting.\n    isOwnedByUs = pendingRequests.remove(request) || isOwnedByUs;\n    if (isOwnedByUs) {\n      request.clear();\n    }\n    return isOwnedByUs;\n  }\n\n  /** Returns {@code true} if requests are currently paused, and {@code false} otherwise. */\n  public boolean isPaused() {\n    return isPaused;\n  }\n\n  /** Stops any in progress requests. */\n  public void pauseRequests() {\n    isPaused = true;\n    for (Request request : Util.getSnapshot(requests)) {\n      if (request.isRunning()) {\n        // Avoid clearing parts of requests that may have completed (thumbnails) to avoid blinking\n        // in the UI, while still making sure that any in progress parts of requests are immediately\n        // stopped.\n        request.pause();\n        pendingRequests.add(request);\n      }\n    }\n  }\n\n  /** Stops any in progress requests and releases bitmaps associated with completed requests. */\n  public void pauseAllRequests() {\n    isPaused = true;\n    for (Request request : Util.getSnapshot(requests)) {\n      // TODO(judds): Failed requests return false from isComplete(). They're still restarted in\n      //  resumeRequests, but they're not cleared here. We should probably clear all requests here?\n      if (request.isRunning() || request.isComplete()) {\n        request.clear();\n        pendingRequests.add(request);\n      }\n    }\n  }\n\n  /** Starts any not yet completed or failed requests. */\n  public void resumeRequests() {\n    isPaused = false;\n    for (Request request : Util.getSnapshot(requests)) {\n      // We don't need to check for cleared here. Any explicit clear by a user will remove the\n      // Request from the tracker, so the only way we'd find a cleared request here is if we cleared\n      // it. As a result it should be safe for us to resume cleared requests.\n      if (!request.isComplete() && !request.isRunning()) {\n        request.begin();\n      }\n    }\n    pendingRequests.clear();\n  }\n\n  /**\n   * Cancels all requests and clears their resources.\n   *\n   * <p>After this call requests cannot be restarted.\n   */\n  public void clearRequests() {\n    for (Request request : Util.getSnapshot(requests)) {\n      // It's unsafe to recycle the Request here because we don't know who might else have a\n      // reference to it.\n      clearAndRemove(request);\n    }\n    pendingRequests.clear();\n  }\n\n  /** Restarts failed requests and cancels and restarts in progress requests. */\n  public void restartRequests() {\n    for (Request request : Util.getSnapshot(requests)) {\n      if (!request.isComplete() && !request.isCleared()) {\n        request.clear();\n        if (!isPaused) {\n          request.begin();\n        } else {\n          // Ensure the request will be restarted in onResume.\n          pendingRequests.add(request);\n        }\n      }\n    }\n  }\n\n  @Override\n  public String toString() {\n    return super.toString() + \"{numRequests=\" + requests.size() + \", isPaused=\" + isPaused + \"}\";\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/SingletonConnectivityReceiver.java",
    "content": "package com.bumptech.glide.manager;\n\nimport android.annotation.SuppressLint;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.net.ConnectivityManager;\nimport android.net.ConnectivityManager.NetworkCallback;\nimport android.net.Network;\nimport android.net.NetworkInfo;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Build.VERSION_CODES;\nimport android.util.Log;\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.manager.ConnectivityMonitor.ConnectivityListener;\nimport com.bumptech.glide.util.GlideSuppliers;\nimport com.bumptech.glide.util.GlideSuppliers.GlideSupplier;\nimport com.bumptech.glide.util.Synthetic;\nimport com.bumptech.glide.util.Util;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.Executor;\n\n/** Uses {@link android.net.ConnectivityManager} to identify connectivity changes. */\nfinal class SingletonConnectivityReceiver {\n  private static volatile SingletonConnectivityReceiver instance;\n  private static final String TAG = \"ConnectivityMonitor\";\n\n  private final FrameworkConnectivityMonitor frameworkConnectivityMonitor;\n\n  @GuardedBy(\"this\")\n  @Synthetic\n  final Set<ConnectivityListener> listeners = new HashSet<ConnectivityListener>();\n\n  @GuardedBy(\"this\")\n  private boolean isRegistered;\n\n  static SingletonConnectivityReceiver get(@NonNull Context context) {\n    if (instance == null) {\n      synchronized (SingletonConnectivityReceiver.class) {\n        if (instance == null) {\n          instance = new SingletonConnectivityReceiver(context.getApplicationContext());\n        }\n      }\n    }\n    return instance;\n  }\n\n  @VisibleForTesting\n  static void reset() {\n    instance = null;\n  }\n\n  private SingletonConnectivityReceiver(final @NonNull Context context) {\n    GlideSupplier<ConnectivityManager> connectivityManager =\n        GlideSuppliers.memorize(\n            new GlideSupplier<ConnectivityManager>() {\n              @Override\n              public ConnectivityManager get() {\n                return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);\n              }\n            });\n    ConnectivityListener connectivityListener =\n        new ConnectivityListener() {\n          @Override\n          public void onConnectivityChanged(boolean isConnected) {\n            Util.assertMainThread();\n            List<ConnectivityListener> toNotify;\n            synchronized (SingletonConnectivityReceiver.this) {\n              toNotify = new ArrayList<>(listeners);\n            }\n            for (ConnectivityListener listener : toNotify) {\n              listener.onConnectivityChanged(isConnected);\n            }\n          }\n        };\n\n    frameworkConnectivityMonitor =\n        Build.VERSION.SDK_INT >= Build.VERSION_CODES.N\n            ? new FrameworkConnectivityMonitorPostApi24(connectivityManager, connectivityListener)\n            : new FrameworkConnectivityMonitorPreApi24(\n                context, connectivityManager, connectivityListener);\n  }\n\n  synchronized void register(ConnectivityListener listener) {\n    listeners.add(listener);\n    maybeRegisterReceiver();\n  }\n\n  /**\n   * To avoid holding a lock while notifying listeners, the unregistered listener may still be\n   * notified about a connectivity change after this method completes if this method is called on a\n   * thread other than the main thread and if a connectivity broadcast is racing with this method.\n   * Callers must handle this case.\n   */\n  synchronized void unregister(ConnectivityListener listener) {\n    listeners.remove(listener);\n    maybeUnregisterReceiver();\n  }\n\n  @GuardedBy(\"this\")\n  private void maybeRegisterReceiver() {\n    if (isRegistered || listeners.isEmpty()) {\n      return;\n    }\n    isRegistered = frameworkConnectivityMonitor.register();\n  }\n\n  @GuardedBy(\"this\")\n  private void maybeUnregisterReceiver() {\n    if (!isRegistered || !listeners.isEmpty()) {\n      return;\n    }\n\n    frameworkConnectivityMonitor.unregister();\n    isRegistered = false;\n  }\n\n  private interface FrameworkConnectivityMonitor {\n    boolean register();\n\n    void unregister();\n  }\n\n  @RequiresApi(VERSION_CODES.N)\n  private static final class FrameworkConnectivityMonitorPostApi24\n      implements FrameworkConnectivityMonitor {\n\n    @Synthetic boolean isConnected;\n    @Synthetic final ConnectivityListener listener;\n    private final GlideSupplier<ConnectivityManager> connectivityManager;\n    private final NetworkCallback networkCallback =\n        new NetworkCallback() {\n          @Override\n          public void onAvailable(@NonNull Network network) {\n            postOnConnectivityChange(true);\n          }\n\n          @Override\n          public void onLost(@NonNull Network network) {\n            postOnConnectivityChange(false);\n          }\n\n          private void postOnConnectivityChange(final boolean newState) {\n            // We could use registerDefaultNetworkCallback with a Handler, but that's only available\n            // on API 26, instead of API 24. We can mimic the same behavior here manually by\n            // posting to the UI thread. All calls have to be posted to make sure that we retain the\n            // original order. Otherwise a call on a background thread, followed by a call on the UI\n            // thread could result in the first call running second.\n            Util.postOnUiThread(\n                new Runnable() {\n                  @Override\n                  public void run() {\n                    onConnectivityChange(newState);\n                  }\n                });\n          }\n\n          @Synthetic\n          void onConnectivityChange(boolean newState) {\n            // See b/201425456.\n            Util.assertMainThread();\n\n            boolean wasConnected = isConnected;\n            isConnected = newState;\n            if (wasConnected != newState) {\n              listener.onConnectivityChanged(newState);\n            }\n          }\n        };\n\n    FrameworkConnectivityMonitorPostApi24(\n        GlideSupplier<ConnectivityManager> connectivityManager, ConnectivityListener listener) {\n      this.connectivityManager = connectivityManager;\n      this.listener = listener;\n    }\n\n    // Permissions are checked in the factory instead.\n    @SuppressLint(\"MissingPermission\")\n    @Override\n    public boolean register() {\n      isConnected = connectivityManager.get().getActiveNetwork() != null;\n      try {\n        connectivityManager.get().registerDefaultNetworkCallback(networkCallback);\n        return true;\n        // See b/201664814, b/204226444: At least TooManyRequestsException is not public and\n        // doesn't extend from any subclass :/.\n      } catch (RuntimeException e) {\n        if (Log.isLoggable(TAG, Log.WARN)) {\n          Log.w(TAG, \"Failed to register callback\", e);\n        }\n        return false;\n      }\n    }\n\n    @Override\n    public void unregister() {\n      connectivityManager.get().unregisterNetworkCallback(networkCallback);\n    }\n  }\n\n  /**\n   * All interactions with connectivity manager and registering/unregistering broadcast receivers\n   * are punted to a background thread. We use serial execution to make sure that they still happen\n   * in the correct order. The system calls required to register/unregister the receiver and to\n   * determine connectivity status are expensive to run on the main thread. isConnected and\n   * isRegistered should only be used on the serial background thread. Listeners should only be\n   * notified on the main thread. Because of the delays caused by punting threads, listeners may be\n   * notified with the incorrect state. Howeve the strict ordering means that they will shortly\n   * after be notified with the correct state.\n   */\n  private static final class FrameworkConnectivityMonitorPreApi24\n      implements FrameworkConnectivityMonitor {\n    // Using AsyncTasks's executor is a hack. We need a background thread, but it's not trivial to\n    // pass one through to this point. We could make a breaking API change, which upsets external\n    // users. Or we could try to add an API to expose one of Glide's executors via the singleton,\n    // but that could allow Glide's executors to be misused as general purpose executors. Given that\n    // this code is deprecated anyway, using some pre-existing general purpose executor doesn't seem\n    // wildly unreasonable.\n    @Synthetic static final Executor EXECUTOR = AsyncTask.SERIAL_EXECUTOR;\n    @Synthetic final Context context;\n    @Synthetic final ConnectivityListener listener;\n    private final GlideSupplier<ConnectivityManager> connectivityManager;\n    // These are only manipulated serially, but the executor might use separate threads to do so,\n    // so we use volatile.\n    @Synthetic volatile boolean isConnected;\n    @Synthetic volatile boolean isRegistered;\n\n    @Synthetic\n    final BroadcastReceiver connectivityReceiver =\n        new BroadcastReceiver() {\n          @Override\n          public void onReceive(@NonNull Context context, Intent intent) {\n            onConnectivityChange();\n          }\n        };\n\n    FrameworkConnectivityMonitorPreApi24(\n        Context context,\n        GlideSupplier<ConnectivityManager> connectivityManager,\n        ConnectivityListener listener) {\n      this.context = context.getApplicationContext();\n      this.connectivityManager = connectivityManager;\n      this.listener = listener;\n    }\n\n    @Override\n    public boolean register() {\n      EXECUTOR.execute(\n          new Runnable() {\n            @Override\n            public void run() {\n              // Initialize isConnected so that we notice the first time around when there's a\n              // broadcast.\n              // TODO(judds): This causes a race where:\n              // 1. Connectivity is disconnected\n              // 2. Register is called, but punted to a background thread and not run yet\n              // 3. Some network requiring request is started, runs and fails due to connectivity\n              // 4. Connectivity is re-established\n              // 5. This code finally runs on the background thread.\n              // In step 5, we'll think that we're currently connected and won't trigger a retry for\n              // any previously failed requests.\n              // In the long run it might be nice to define some explicit initialization step for\n              // Glide where we do this and other expensive things on a background thread prior to\n              // starting the first request. For now it seems better to just accept this race than\n              // either take the latency hit of the IPC on the main thread, or try something like\n              // always notifying all listeners once right after this logic runs just in case\n              // something failed.\n              isConnected = isConnected();\n              try {\n                // See #1405\n                context.registerReceiver(\n                    connectivityReceiver,\n                    new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));\n                isRegistered = true;\n              } catch (SecurityException e) {\n                // See #1417, registering the receiver can throw SecurityException.\n                if (Log.isLoggable(TAG, Log.WARN)) {\n                  Log.w(TAG, \"Failed to register\", e);\n                }\n                isRegistered = false;\n              }\n            }\n          });\n\n      // We track our registration status internally, so we always need to be called to unregister.\n      return true;\n    }\n\n    @Override\n    public void unregister() {\n      // Always post to the executor to make sure everything runs in the correct order. If we short\n      // circuit that by checking isConnected on this thread, we might leak a receiver.\n      EXECUTOR.execute(\n          new Runnable() {\n            @Override\n            public void run() {\n              if (!isRegistered) {\n                return;\n              }\n              isRegistered = false;\n              context.unregisterReceiver(connectivityReceiver);\n            }\n          });\n    }\n\n    @Synthetic\n    void onConnectivityChange() {\n      EXECUTOR.execute(\n          new Runnable() {\n            @Override\n            public void run() {\n              boolean wasConnected = isConnected;\n              isConnected = isConnected();\n              if (wasConnected != isConnected) {\n                if (Log.isLoggable(TAG, Log.DEBUG)) {\n                  Log.d(TAG, \"connectivity changed, isConnected: \" + isConnected);\n                }\n\n                notifyChangeOnUiThread(isConnected);\n              }\n            }\n          });\n    }\n\n    // Pass through the boolean because the instance variable could change while we're waiting for\n    // the runnable to be executed on the main thread.\n    @Synthetic\n    void notifyChangeOnUiThread(final boolean isConnected) {\n      Util.postOnUiThread(\n          new Runnable() {\n            @Override\n            public void run() {\n              listener.onConnectivityChanged(isConnected);\n            }\n          });\n    }\n\n    @SuppressWarnings(\"WeakerAccess\")\n    @Synthetic\n    // Permissions are checked in the factory instead.\n    @SuppressLint(\"MissingPermission\")\n    boolean isConnected() {\n      NetworkInfo networkInfo;\n      try {\n        networkInfo = connectivityManager.get().getActiveNetworkInfo();\n      } catch (RuntimeException e) {\n        // #1405 shows that this throws a SecurityException.\n        // b/70869360 shows that this throws NullPointerException on APIs 22, 23, and 24.\n        // b/70869360 also shows that this throws RuntimeException on API 24 and 25.\n        if (Log.isLoggable(TAG, Log.WARN)) {\n          Log.w(TAG, \"Failed to determine connectivity status when connectivity changed\", e);\n        }\n        // Default to true;\n        return true;\n      }\n      return networkInfo != null && networkInfo.isConnected();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/SupportRequestManagerFragment.java",
    "content": "package com.bumptech.glide.manager;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport com.bumptech.glide.RequestManager;\n\n/**\n * A view-less {@link androidx.fragment.app.Fragment} used to safely store an {@link\n * com.bumptech.glide.RequestManager} that can be used to start, stop and manage Glide requests\n * started for targets within the fragment or activity this fragment is a child of.\n *\n * @see com.bumptech.glide.manager.RequestManagerRetriever\n * @see com.bumptech.glide.RequestManager\n * @deprecated This class is unused by Glide. All functionality has been removed. The class will be\n *     removed in a future version.\n */\n@Deprecated\npublic class SupportRequestManagerFragment extends Fragment {\n\n  /**\n   * @deprecated A no-op method. See the class deprecation method for details.\n   */\n  @Deprecated\n  public void setRequestManager(@Nullable RequestManager requestManager) {}\n\n  /**\n   * @deprecated Always returns {@code null}. See the class deprecation method for details.\n   */\n  @Nullable\n  @Deprecated\n  public RequestManager getRequestManager() {\n    return null;\n  }\n\n  /**\n   * @deprecated Always returns {@link EmptyRequestManagerTreeNode}. See the class deprecation\n   *     method for details.\n   */\n  @Deprecated\n  @NonNull\n  public RequestManagerTreeNode getRequestManagerTreeNode() {\n    return new EmptyRequestManagerTreeNode();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/manager/TargetTracker.java",
    "content": "package com.bumptech.glide.manager;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.Util;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.WeakHashMap;\n\n/**\n * Holds the set of {@link Target}s currently active for a {@link com.bumptech.glide.RequestManager}\n * and forwards on lifecycle events.\n */\npublic final class TargetTracker implements LifecycleListener {\n  private final Set<Target<?>> targets =\n      Collections.newSetFromMap(new WeakHashMap<Target<?>, Boolean>());\n\n  public void track(@NonNull Target<?> target) {\n    targets.add(target);\n  }\n\n  public void untrack(@NonNull Target<?> target) {\n    targets.remove(target);\n  }\n\n  @Override\n  public void onStart() {\n    for (Target<?> target : Util.getSnapshot(targets)) {\n      target.onStart();\n    }\n  }\n\n  @Override\n  public void onStop() {\n    for (Target<?> target : Util.getSnapshot(targets)) {\n      target.onStop();\n    }\n  }\n\n  @Override\n  public void onDestroy() {\n    for (Target<?> target : Util.getSnapshot(targets)) {\n      target.onDestroy();\n    }\n  }\n\n  @NonNull\n  public List<Target<?>> getAll() {\n    return Util.getSnapshot(targets);\n  }\n\n  public void clear() {\n    targets.clear();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/module/AppGlideModule.java",
    "content": "package com.bumptech.glide.module;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.GlideBuilder;\n\n/**\n * Defines a set of dependencies and options to use when initializing Glide within an application.\n *\n * <p>There can be at most one {@link AppGlideModule} in an application. Only Applications can\n * include a {@link AppGlideModule}. Libraries must use {@link LibraryGlideModule}.\n *\n * <p>Classes that extend {@link AppGlideModule} must be annotated with {@link\n * com.bumptech.glide.annotation.GlideModule} to be processed correctly.\n *\n * <p>Classes that extend {@link AppGlideModule} can optionally be annotated with {@link\n * com.bumptech.glide.annotation.Excludes} to optionally exclude one or more {@link\n * LibraryGlideModule} and/or {@link GlideModule} classes.\n *\n * <p>Once an application has migrated itself and all libraries it depends on to use Glide's\n * annotation processor, {@link AppGlideModule} implementations should override {@link\n * #isManifestParsingEnabled()} and return {@code false}.\n */\n// Used only in javadoc.\n@SuppressWarnings(\"deprecation\")\npublic abstract class AppGlideModule extends LibraryGlideModule implements AppliesOptions {\n  /**\n   * Returns {@code true} if Glide should check the AndroidManifest for {@link GlideModule}s.\n   *\n   * <p>Implementations should return {@code false} after they and their dependencies have migrated\n   * to Glide's annotation processor.\n   *\n   * <p>Returns {@code true} by default.\n   */\n  public boolean isManifestParsingEnabled() {\n    return true;\n  }\n\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    // Default empty impl.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/module/AppliesOptions.java",
    "content": "package com.bumptech.glide.module;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.GlideBuilder;\n\n/** An internal interface, to be removed when {@link GlideModule}s are removed. */\n@Deprecated\ninterface AppliesOptions {\n  /**\n   * Lazily apply options to a {@link com.bumptech.glide.GlideBuilder} immediately before the Glide\n   * singleton is created.\n   *\n   * <p>This method will be called once and only once per implementation.\n   *\n   * @param context An Application {@link android.content.Context}.\n   * @param builder The {@link com.bumptech.glide.GlideBuilder} that will be used to create Glide.\n   */\n  void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/module/GlideModule.java",
    "content": "package com.bumptech.glide.module;\n\nimport com.bumptech.glide.Registry;\n\n/**\n * An interface allowing lazy configuration of Glide including setting options using {@link\n * com.bumptech.glide.GlideBuilder} and registering {@link com.bumptech.glide.load.model.ModelLoader\n * ModelLoaders}.\n *\n * <p>To use this interface:\n *\n * <ol>\n *   <li>Implement the GlideModule interface in a class with public visibility, calling {@link\n *       Registry#prepend(Class, Class, com.bumptech.glide.load.ResourceDecoder)} for each {@link\n *       com.bumptech.glide.load.model.ModelLoader} you'd like to register:\n *       <pre>\n *                  <code>\n *                      public class FlickrGlideModule implements GlideModule {\n *                          {@literal @}Override\n *                          public void applyOptions(Context context, GlideBuilder builder) {\n *                              builder.setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888);\n *                          }\n *\n *                          {@literal @}Override\n *                          public void registerComponents(Context context, Glide glide) {\n *                              glide.register(Model.class, Data.class, new MyModelLoader());\n *                          }\n *                      }\n *                  </code>\n *             </pre>\n *   <li>Add your implementation to your list of keeps in your proguard.cfg file:\n *       <pre>{@code\n * -keepnames class * com.bumptech.glide.samples.flickr.FlickrGlideModule\n * }</pre>\n *   <li>Add a metadata tag to your AndroidManifest.xml with your GlideModule implementation's fully\n *       qualified classname as the key, and {@code GlideModule} as the value:\n *       <pre>{@code\n * <meta-data\n *     android:name=\"com.bumptech.glide.samples.flickr.FlickrGlideModule\"\n *     android:value=\"GlideModule\" />\n * }</pre>\n * </ol>\n *\n * <p>All implementations must be publicly visible and contain only an empty constructor so they can\n * be instantiated via reflection when Glide is lazily initialized.\n *\n * <p>There is no defined order in which modules are called, so projects should be careful to avoid\n * applying conflicting settings in different modules. If an application depends on libraries that\n * have conflicting modules, the application should consider avoiding the library modules and\n * instead providing their required dependencies in a single application module.\n *\n * @deprecated Libraries should use {@link LibraryGlideModule} and Applications should use {@link\n *     AppGlideModule}.\n */\n@Deprecated\npublic interface GlideModule extends RegistersComponents, AppliesOptions {}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/module/LibraryGlideModule.java",
    "content": "package com.bumptech.glide.module;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\n\n/**\n * Registers a set of components to use when initializing Glide within an app when Glide's\n * annotation processor is used.\n *\n * <p>Any number of LibraryGlideModules can be contained within any library or application.\n *\n * <p>LibraryGlideModules are called in no defined order. If LibraryGlideModules within an\n * application conflict, {@link AppGlideModule}s can use the {@link\n * com.bumptech.glide.annotation.Excludes} annotation to selectively remove one or more of the\n * conflicting modules.\n */\n@SuppressWarnings(\"deprecation\")\npublic abstract class LibraryGlideModule implements RegistersComponents {\n  @Override\n  public void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    // Default empty impl.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/module/ManifestParser.java",
    "content": "package com.bumptech.glide.module;\n\nimport android.content.Context;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.util.Log;\nimport androidx.annotation.Nullable;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Parses {@link com.bumptech.glide.module.GlideModule} references out of the AndroidManifest file.\n */\n// Used only in javadoc.\n@SuppressWarnings(\"deprecation\")\n@Deprecated\npublic final class ManifestParser {\n  private static final String TAG = \"ManifestParser\";\n  private static final String GLIDE_MODULE_VALUE = \"GlideModule\";\n\n  private final Context context;\n\n  public ManifestParser(Context context) {\n    this.context = context;\n  }\n\n  // getApplicationInfo returns null in Compose previews, see #4977 and b/263613353.\n  @SuppressWarnings(\"ConstantConditions\")\n  @Nullable\n  private ApplicationInfo getOurApplicationInfo() throws NameNotFoundException {\n    return context\n        .getPackageManager()\n        .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  public List<GlideModule> parse() {\n    if (Log.isLoggable(TAG, Log.DEBUG)) {\n      Log.d(TAG, \"Loading Glide modules\");\n    }\n    List<GlideModule> modules = new ArrayList<>();\n    try {\n      ApplicationInfo appInfo = getOurApplicationInfo();\n      if (appInfo == null || appInfo.metaData == null) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"Got null app info metadata\");\n        }\n        return modules;\n      }\n      if (Log.isLoggable(TAG, Log.VERBOSE)) {\n        Log.v(TAG, \"Got app info metadata: \" + appInfo.metaData);\n      }\n      for (String key : appInfo.metaData.keySet()) {\n        if (GLIDE_MODULE_VALUE.equals(appInfo.metaData.get(key))) {\n          modules.add(parseModule(key));\n          if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(TAG, \"Loaded Glide module: \" + key);\n          }\n        }\n      }\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Finished loading Glide modules\");\n      }\n    } catch (PackageManager.NameNotFoundException e) {\n      if (Log.isLoggable(TAG, Log.ERROR)) {\n        Log.e(TAG, \"Failed to parse glide modules\", e);\n      }\n    }\n\n    return modules;\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  private static GlideModule parseModule(String className) {\n    Class<?> clazz;\n    try {\n      clazz = Class.forName(className);\n    } catch (ClassNotFoundException e) {\n      throw new IllegalArgumentException(\"Unable to find GlideModule implementation\", e);\n    }\n\n    Object module = null;\n    try {\n      module = clazz.getDeclaredConstructor().newInstance();\n      // These can't be combined until API minimum is 19.\n    } catch (InstantiationException e) {\n      throwInstantiateGlideModuleException(clazz, e);\n    } catch (IllegalAccessException e) {\n      throwInstantiateGlideModuleException(clazz, e);\n    } catch (NoSuchMethodException e) {\n      throwInstantiateGlideModuleException(clazz, e);\n    } catch (InvocationTargetException e) {\n      throwInstantiateGlideModuleException(clazz, e);\n    }\n\n    if (!(module instanceof GlideModule)) {\n      throw new RuntimeException(\"Expected instanceof GlideModule, but found: \" + module);\n    }\n    return (GlideModule) module;\n  }\n\n  private static void throwInstantiateGlideModuleException(Class<?> clazz, Exception e) {\n    throw new RuntimeException(\"Unable to instantiate GlideModule implementation for \" + clazz, e);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/module/RegistersComponents.java",
    "content": "package com.bumptech.glide.module;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\n\n/** An internal interface, to be removed when {@link GlideModule}s are removed. */\n// Used only in javadocs.\n@SuppressWarnings(\"deprecation\")\n@Deprecated\ninterface RegistersComponents {\n\n  /**\n   * Lazily register components immediately after the Glide singleton is created but before any\n   * requests can be started.\n   *\n   * <p>This method will be called once and only once per implementation.\n   *\n   * @param context An Application {@link android.content.Context}.\n   * @param glide The Glide singleton that is in the process of being initialized.\n   * @param registry An {@link com.bumptech.glide.Registry} to use to register components.\n   */\n  void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/provider/EncoderRegistry.java",
    "content": "package com.bumptech.glide.provider;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Encoder;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/** Contains an ordered list of {@link Encoder}s capable of encoding arbitrary data types. */\npublic class EncoderRegistry {\n  // TODO: This registry should probably contain a put, rather than a list.\n  private final List<Entry<?>> encoders = new ArrayList<>();\n\n  @SuppressWarnings(\"unchecked\")\n  @Nullable\n  public synchronized <T> Encoder<T> getEncoder(@NonNull Class<T> dataClass) {\n    for (Entry<?> entry : encoders) {\n      if (entry.handles(dataClass)) {\n        return (Encoder<T>) entry.encoder;\n      }\n    }\n    return null;\n  }\n\n  public synchronized <T> void append(@NonNull Class<T> dataClass, @NonNull Encoder<T> encoder) {\n    encoders.add(new Entry<>(dataClass, encoder));\n  }\n\n  public synchronized <T> void prepend(@NonNull Class<T> dataClass, @NonNull Encoder<T> encoder) {\n    encoders.add(0, new Entry<>(dataClass, encoder));\n  }\n\n  private static final class Entry<T> {\n    private final Class<T> dataClass;\n\n    @Synthetic\n    @SuppressWarnings(\"WeakerAccess\")\n    final Encoder<T> encoder;\n\n    Entry(@NonNull Class<T> dataClass, @NonNull Encoder<T> encoder) {\n      this.dataClass = dataClass;\n      this.encoder = encoder;\n    }\n\n    boolean handles(@NonNull Class<?> dataClass) {\n      return this.dataClass.isAssignableFrom(dataClass);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/provider/ImageHeaderParserRegistry.java",
    "content": "package com.bumptech.glide.provider;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/** Contains an unordered list of {@link ImageHeaderParser}s capable of parsing image headers. */\npublic final class ImageHeaderParserRegistry {\n  private final List<ImageHeaderParser> parsers = new ArrayList<>();\n\n  @NonNull\n  public synchronized List<ImageHeaderParser> getParsers() {\n    return parsers;\n  }\n\n  public synchronized void add(@NonNull ImageHeaderParser parser) {\n    parsers.add(parser);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/provider/LoadPathCache.java",
    "content": "package com.bumptech.glide.provider;\n\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArrayMap;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.DecodePath;\nimport com.bumptech.glide.load.engine.LoadPath;\nimport com.bumptech.glide.load.resource.transcode.UnitTranscoder;\nimport com.bumptech.glide.util.MultiClassKey;\nimport java.util.Collections;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Maintains a cache of data, resource, and transcode classes to available {@link\n * com.bumptech.glide.load.engine.LoadPath}s capable of decoding with the requested types.\n */\npublic class LoadPathCache {\n  private static final LoadPath<?, ?, ?> NO_PATHS_SIGNAL =\n      new LoadPath<>(\n          Object.class,\n          Object.class,\n          Object.class,\n          Collections.singletonList(\n              new DecodePath<>(\n                  Object.class,\n                  Object.class,\n                  Object.class,\n                  Collections.<ResourceDecoder<Object, Object>>emptyList(),\n                  new UnitTranscoder<>(),\n                  /* listPool= */ null)),\n          /* listPool= */ null);\n\n  private final ArrayMap<MultiClassKey, LoadPath<?, ?, ?>> cache = new ArrayMap<>();\n  private final AtomicReference<MultiClassKey> keyRef = new AtomicReference<>();\n\n  /**\n   * Returns {@code} true if the given {@link LoadPath} is the signal object returned from {@link\n   * #get(Class, Class, Class)} that indicates that we've previously found that there are no\n   * available paths to load the requested resources and {@code false} otherwise.\n   */\n  public boolean isEmptyLoadPath(@Nullable LoadPath<?, ?, ?> path) {\n    return NO_PATHS_SIGNAL.equals(path);\n  }\n\n  /**\n   * May return {@link #NO_PATHS_SIGNAL} to indicate that we've previously found that there are 0\n   * available load paths for the requested types. Callers must check using {@link\n   * #isEmptyLoadPath(LoadPath)} before using any load path returned by this method.\n   */\n  @SuppressWarnings(\"unchecked\")\n  @Nullable\n  public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> get(\n      Class<Data> dataClass, Class<TResource> resourceClass, Class<Transcode> transcodeClass) {\n    MultiClassKey key = getKey(dataClass, resourceClass, transcodeClass);\n    LoadPath<?, ?, ?> result;\n    synchronized (cache) {\n      result = cache.get(key);\n    }\n    keyRef.set(key);\n\n    return (LoadPath<Data, TResource, Transcode>) result;\n  }\n\n  public void put(\n      Class<?> dataClass,\n      Class<?> resourceClass,\n      Class<?> transcodeClass,\n      @Nullable LoadPath<?, ?, ?> loadPath) {\n    synchronized (cache) {\n      cache.put(\n          new MultiClassKey(dataClass, resourceClass, transcodeClass),\n          loadPath != null ? loadPath : NO_PATHS_SIGNAL);\n    }\n  }\n\n  private MultiClassKey getKey(\n      Class<?> dataClass, Class<?> resourceClass, Class<?> transcodeClass) {\n    MultiClassKey key = keyRef.getAndSet(null);\n    if (key == null) {\n      key = new MultiClassKey();\n    }\n    key.set(dataClass, resourceClass, transcodeClass);\n    return key;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/provider/ModelToResourceClassCache.java",
    "content": "package com.bumptech.glide.provider;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArrayMap;\nimport com.bumptech.glide.util.MultiClassKey;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Maintains a cache of Model + Resource class to a set of registered resource classes that are\n * subclasses of the resource class that can be decoded from the model class.\n */\npublic class ModelToResourceClassCache {\n  private final AtomicReference<MultiClassKey> resourceClassKeyRef = new AtomicReference<>();\n  private final ArrayMap<MultiClassKey, List<Class<?>>> registeredResourceClassCache =\n      new ArrayMap<>();\n\n  @Nullable\n  public List<Class<?>> get(\n      @NonNull Class<?> modelClass,\n      @NonNull Class<?> resourceClass,\n      @NonNull Class<?> transcodeClass) {\n    MultiClassKey key = resourceClassKeyRef.getAndSet(null);\n    if (key == null) {\n      key = new MultiClassKey(modelClass, resourceClass, transcodeClass);\n    } else {\n      key.set(modelClass, resourceClass, transcodeClass);\n    }\n    final List<Class<?>> result;\n    synchronized (registeredResourceClassCache) {\n      result = registeredResourceClassCache.get(key);\n    }\n    resourceClassKeyRef.set(key);\n    return result;\n  }\n\n  public void put(\n      @NonNull Class<?> modelClass,\n      @NonNull Class<?> resourceClass,\n      @NonNull Class<?> transcodeClass,\n      @NonNull List<Class<?>> resourceClasses) {\n    synchronized (registeredResourceClassCache) {\n      registeredResourceClassCache.put(\n          new MultiClassKey(modelClass, resourceClass, transcodeClass), resourceClasses);\n    }\n  }\n\n  public void clear() {\n    synchronized (registeredResourceClassCache) {\n      registeredResourceClassCache.clear();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/provider/ResourceDecoderRegistry.java",
    "content": "package com.bumptech.glide.provider;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Contains an ordered list of {@link ResourceDecoder}s capable of decoding arbitrary data types\n * into arbitrary resource types from highest priority decoders to lowest priority decoders.\n */\n@SuppressWarnings(\"rawtypes\")\npublic class ResourceDecoderRegistry {\n  private final List<String> bucketPriorityList = new ArrayList<>();\n  private final Map<String, List<Entry<?, ?>>> decoders = new HashMap<>();\n\n  public synchronized void setBucketPriorityList(@NonNull List<String> buckets) {\n    List<String> previousBuckets = new ArrayList<>(bucketPriorityList);\n    bucketPriorityList.clear();\n    // new ArrayList(List) and ArrayList#addAll(List) are both broken on some verisons of Android,\n    // see #3296\n    for (String bucket : buckets) {\n      bucketPriorityList.add(bucket);\n    }\n    for (String previousBucket : previousBuckets) {\n      if (!buckets.contains(previousBucket)) {\n        // Keep any buckets from the previous list that aren't included here, but but them at the\n        // end.\n        bucketPriorityList.add(previousBucket);\n      }\n    }\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  public synchronized <T, R> List<ResourceDecoder<T, R>> getDecoders(\n      @NonNull Class<T> dataClass, @NonNull Class<R> resourceClass) {\n    List<ResourceDecoder<T, R>> result = new ArrayList<>();\n    for (String bucket : bucketPriorityList) {\n      List<Entry<?, ?>> entries = decoders.get(bucket);\n      if (entries == null) {\n        continue;\n      }\n      for (Entry<?, ?> entry : entries) {\n        if (entry.handles(dataClass, resourceClass)) {\n          result.add((ResourceDecoder<T, R>) entry.decoder);\n        }\n      }\n    }\n    // TODO: cache result list.\n\n    return result;\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  public synchronized <T, R> List<Class<R>> getResourceClasses(\n      @NonNull Class<T> dataClass, @NonNull Class<R> resourceClass) {\n    List<Class<R>> result = new ArrayList<>();\n    for (String bucket : bucketPriorityList) {\n      List<Entry<?, ?>> entries = decoders.get(bucket);\n      if (entries == null) {\n        continue;\n      }\n      for (Entry<?, ?> entry : entries) {\n        if (entry.handles(dataClass, resourceClass)\n            && !result.contains((Class<R>) entry.resourceClass)) {\n          result.add((Class<R>) entry.resourceClass);\n        }\n      }\n    }\n    return result;\n  }\n\n  public synchronized <T, R> void append(\n      @NonNull String bucket,\n      @NonNull ResourceDecoder<T, R> decoder,\n      @NonNull Class<T> dataClass,\n      @NonNull Class<R> resourceClass) {\n    getOrAddEntryList(bucket).add(new Entry<>(dataClass, resourceClass, decoder));\n  }\n\n  public synchronized <T, R> void prepend(\n      @NonNull String bucket,\n      @NonNull ResourceDecoder<T, R> decoder,\n      @NonNull Class<T> dataClass,\n      @NonNull Class<R> resourceClass) {\n    getOrAddEntryList(bucket).add(0, new Entry<>(dataClass, resourceClass, decoder));\n  }\n\n  @NonNull\n  private synchronized List<Entry<?, ?>> getOrAddEntryList(@NonNull String bucket) {\n    if (!bucketPriorityList.contains(bucket)) {\n      // Add this unspecified bucket as a low priority bucket.\n      bucketPriorityList.add(bucket);\n    }\n    List<Entry<?, ?>> entries = decoders.get(bucket);\n    if (entries == null) {\n      entries = new ArrayList<>();\n      decoders.put(bucket, entries);\n    }\n    return entries;\n  }\n\n  private static class Entry<T, R> {\n    private final Class<T> dataClass;\n    @Synthetic final Class<R> resourceClass;\n    @Synthetic final ResourceDecoder<T, R> decoder;\n\n    public Entry(\n        @NonNull Class<T> dataClass,\n        @NonNull Class<R> resourceClass,\n        ResourceDecoder<T, R> decoder) {\n      this.dataClass = dataClass;\n      this.resourceClass = resourceClass;\n      this.decoder = decoder;\n    }\n\n    public boolean handles(@NonNull Class<?> dataClass, @NonNull Class<?> resourceClass) {\n      return this.dataClass.isAssignableFrom(dataClass)\n          && resourceClass.isAssignableFrom(this.resourceClass);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/provider/ResourceEncoderRegistry.java",
    "content": "package com.bumptech.glide.provider;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.ResourceEncoder;\nimport com.bumptech.glide.util.Synthetic;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Contains an ordered list of {@link ResourceEncoder}s capable of encoding arbitrary resource\n * types.\n */\npublic class ResourceEncoderRegistry {\n  // TODO: this should probably be a put.\n  private final List<Entry<?>> encoders = new ArrayList<>();\n\n  public synchronized <Z> void append(\n      @NonNull Class<Z> resourceClass, @NonNull ResourceEncoder<Z> encoder) {\n    encoders.add(new Entry<>(resourceClass, encoder));\n  }\n\n  public synchronized <Z> void prepend(\n      @NonNull Class<Z> resourceClass, @NonNull ResourceEncoder<Z> encoder) {\n    encoders.add(0, new Entry<>(resourceClass, encoder));\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Nullable\n  public synchronized <Z> ResourceEncoder<Z> get(@NonNull Class<Z> resourceClass) {\n    //noinspection ForLoopReplaceableByForEach to improve perf\n    for (int i = 0, size = encoders.size(); i < size; i++) {\n      Entry<?> entry = encoders.get(i);\n      if (entry.handles(resourceClass)) {\n        return (ResourceEncoder<Z>) entry.encoder;\n      }\n    }\n    // TODO: throw an exception here?\n    return null;\n  }\n\n  private static final class Entry<T> {\n    private final Class<T> resourceClass;\n    @Synthetic final ResourceEncoder<T> encoder;\n\n    Entry(@NonNull Class<T> resourceClass, @NonNull ResourceEncoder<T> encoder) {\n      this.resourceClass = resourceClass;\n      this.encoder = encoder;\n    }\n\n    @Synthetic\n    boolean handles(@NonNull Class<?> resourceClass) {\n      return this.resourceClass.isAssignableFrom(resourceClass);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/BaseRequestOptions.java",
    "content": "package com.bumptech.glide.request;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.MultiTransformation;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.model.stream.HttpGlideUrlLoader;\nimport com.bumptech.glide.load.resource.bitmap.BitmapEncoder;\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop;\nimport com.bumptech.glide.load.resource.bitmap.CenterInside;\nimport com.bumptech.glide.load.resource.bitmap.CircleCrop;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.load.resource.bitmap.Downsampler;\nimport com.bumptech.glide.load.resource.bitmap.DrawableTransformation;\nimport com.bumptech.glide.load.resource.bitmap.FitCenter;\nimport com.bumptech.glide.load.resource.bitmap.VideoDecoder;\nimport com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.load.resource.gif.GifDrawableTransformation;\nimport com.bumptech.glide.load.resource.gif.GifOptions;\nimport com.bumptech.glide.signature.EmptySignature;\nimport com.bumptech.glide.util.CachedHashCodeArrayMap;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Util;\nimport java.util.Map;\n\n/**\n * A base object to allow method sharing between {@link RequestOptions} and {@link\n * com.bumptech.glide.RequestBuilder}.\n *\n * <p>This class is not meant for general use and may change at any time.\n *\n * @param <T> The particular child implementation\n */\n@SuppressWarnings({\"PMD.UseUtilityClass\", \"unused\"})\npublic abstract class BaseRequestOptions<T extends BaseRequestOptions<T>> implements Cloneable {\n  private static final int UNSET = -1;\n  private static final int SIZE_MULTIPLIER = 1 << 1;\n  private static final int DISK_CACHE_STRATEGY = 1 << 2;\n  private static final int PRIORITY = 1 << 3;\n  private static final int ERROR_PLACEHOLDER = 1 << 4;\n  private static final int ERROR_ID = 1 << 5;\n  private static final int PLACEHOLDER = 1 << 6;\n  private static final int PLACEHOLDER_ID = 1 << 7;\n  private static final int IS_CACHEABLE = 1 << 8;\n  private static final int OVERRIDE = 1 << 9;\n  private static final int SIGNATURE = 1 << 10;\n  private static final int TRANSFORMATION = 1 << 11;\n  private static final int RESOURCE_CLASS = 1 << 12;\n  private static final int FALLBACK = 1 << 13;\n  private static final int FALLBACK_ID = 1 << 14;\n  private static final int THEME = 1 << 15;\n  private static final int TRANSFORMATION_ALLOWED = 1 << 16;\n  private static final int TRANSFORMATION_REQUIRED = 1 << 17;\n  private static final int USE_UNLIMITED_SOURCE_GENERATORS_POOL = 1 << 18;\n  private static final int ONLY_RETRIEVE_FROM_CACHE = 1 << 19;\n  private static final int USE_ANIMATION_POOL = 1 << 20;\n\n  private int fields;\n  private float sizeMultiplier = 1f;\n  @NonNull private DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.AUTOMATIC;\n  @NonNull private Priority priority = Priority.NORMAL;\n  @Nullable private Drawable errorPlaceholder;\n  private int errorId;\n  @Nullable private Drawable placeholderDrawable;\n  private int placeholderId;\n  private boolean isCacheable = true;\n  private int overrideHeight = UNSET;\n  private int overrideWidth = UNSET;\n  @NonNull private Key signature = EmptySignature.obtain();\n  private boolean isTransformationRequired;\n  private boolean isTransformationAllowed = true;\n  @Nullable private Drawable fallbackDrawable;\n  private int fallbackId;\n  @NonNull private Options options = new Options();\n\n  @NonNull\n  private Map<Class<?>, Transformation<?>> transformations = new CachedHashCodeArrayMap<>();\n\n  @NonNull private Class<?> resourceClass = Object.class;\n  private boolean isLocked;\n  @Nullable private Resources.Theme theme;\n  private boolean isAutoCloneEnabled;\n  private boolean useUnlimitedSourceGeneratorsPool;\n  private boolean onlyRetrieveFromCache;\n  private boolean isScaleOnlyOrNoTransform = true;\n  private boolean useAnimationPool;\n\n  private static boolean isSet(int fields, int flag) {\n    return (fields & flag) != 0;\n  }\n\n  /**\n   * Applies a multiplier to the {@link com.bumptech.glide.request.target.Target}'s size before\n   * loading the resource. Useful for loading thumbnails or trying to avoid loading huge resources\n   * (particularly {@link Bitmap}s on devices with overly dense screens.\n   *\n   * @param sizeMultiplier The multiplier to apply to the {@link\n   *     com.bumptech.glide.request.target.Target}'s dimensions when loading the resource.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T sizeMultiplier(@FloatRange(from = 0, to = 1) float sizeMultiplier) {\n    if (isAutoCloneEnabled) {\n      return clone().sizeMultiplier(sizeMultiplier);\n    }\n\n    if (sizeMultiplier < 0f || sizeMultiplier > 1f) {\n      throw new IllegalArgumentException(\"sizeMultiplier must be between 0 and 1\");\n    }\n    this.sizeMultiplier = sizeMultiplier;\n    fields |= SIZE_MULTIPLIER;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * If set to {@code true}, uses a cached unlimited {@link java.util.concurrent.Executor} to run\n   * the request.\n   *\n   * <p>This method should <em>ONLY</em> be used when a Glide load is started recursively on one of\n   * Glide's threads as part of another request. Using this method in other scenarios can lead to\n   * excessive memory usage and OOMs and/or a significant decrease in performance across an\n   * application.\n   *\n   * <p>If both this method and {@link #useAnimationPool(boolean)} are set, this method will be\n   * preferred and {@link #useAnimationPool(boolean)} will be ignored.\n   */\n  @NonNull\n  @CheckResult\n  public T useUnlimitedSourceGeneratorsPool(boolean flag) {\n    if (isAutoCloneEnabled) {\n      return clone().useUnlimitedSourceGeneratorsPool(flag);\n    }\n\n    this.useUnlimitedSourceGeneratorsPool = flag;\n    fields |= USE_UNLIMITED_SOURCE_GENERATORS_POOL;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * If set to {@code true}, uses a special {@link java.util.concurrent.Executor} that is used\n   * exclusively for decoding frames of animated resources, like GIFs.\n   *\n   * <p>The animation executor disallows network operations and must not be used for loads that may\n   * load remote data. The animation executor has fewer threads available to it than Glide's normal\n   * executors and is only useful as a way of avoiding blocking on longer and more expensive reads\n   * for critical requests like those in an animating GIF.\n   *\n   * <p>If both {@link #useUnlimitedSourceGeneratorsPool(boolean)} and this method are set, {@link\n   * #useUnlimitedSourceGeneratorsPool(boolean)} will be preferred and this method will be ignored.\n   */\n  @NonNull\n  @CheckResult\n  public T useAnimationPool(boolean flag) {\n    if (isAutoCloneEnabled) {\n      return clone().useAnimationPool(flag);\n    }\n\n    useAnimationPool = flag;\n    fields |= USE_ANIMATION_POOL;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * If set to true, will only load an item if found in the cache, and will not fetch from source.\n   *\n   * <p>By 'cache' we mean both the in memory cache and both types of disk cache ({@link\n   * DiskCacheStrategy#DATA} and {@link DiskCacheStrategy#RESOURCE}). If this flag is set to {@code\n   * true} and the item is not in the memory cache, but it is in one of the disk caches, the load\n   * will complete asynchronously.\n   *\n   * <p>If you'd like to only load an item from the memory cache. You can call this method with\n   * {@code true} and also call {@link #diskCacheStrategy(DiskCacheStrategy)} with {@link\n   * DiskCacheStrategy#NONE}\n   */\n  @NonNull\n  @CheckResult\n  public T onlyRetrieveFromCache(boolean flag) {\n    if (isAutoCloneEnabled) {\n      return clone().onlyRetrieveFromCache(flag);\n    }\n\n    this.onlyRetrieveFromCache = flag;\n    fields |= ONLY_RETRIEVE_FROM_CACHE;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets the {@link DiskCacheStrategy} to use for this load.\n   *\n   * <p>Defaults to {@link DiskCacheStrategy#AUTOMATIC}.\n   *\n   * <p>For most applications {@link DiskCacheStrategy#RESOURCE} is ideal. Applications that use the\n   * same resource multiple times in multiple sizes and are willing to trade off some speed and disk\n   * space in return for lower bandwidth usage may want to consider using {@link\n   * DiskCacheStrategy#DATA} or {@link DiskCacheStrategy#ALL}.\n   *\n   * @param strategy The strategy to use.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T diskCacheStrategy(@NonNull DiskCacheStrategy strategy) {\n    if (isAutoCloneEnabled) {\n      return clone().diskCacheStrategy(strategy);\n    }\n    this.diskCacheStrategy = Preconditions.checkNotNull(strategy);\n    fields |= DISK_CACHE_STRATEGY;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets the priority for this load.\n   *\n   * @param priority A priority.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T priority(@NonNull Priority priority) {\n    if (isAutoCloneEnabled) {\n      return clone().priority(priority);\n    }\n\n    this.priority = Preconditions.checkNotNull(priority);\n    fields |= PRIORITY;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets an {@link Drawable} to display while a resource is loading.\n   *\n   * <p>Replaces any previous calls to this method or {@link #placeholder(int)}.\n   *\n   * @param drawable The drawable to display as a placeholder.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T placeholder(@Nullable Drawable drawable) {\n    if (isAutoCloneEnabled) {\n      return clone().placeholder(drawable);\n    }\n\n    this.placeholderDrawable = drawable;\n    fields |= PLACEHOLDER;\n\n    placeholderId = 0;\n    fields &= ~PLACEHOLDER_ID;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets an Android resource id for a {@link Drawable} resource to display while a resource is\n   * loading.\n   *\n   * <p>Replaces any previous calls to this method or {@link #placeholder(Drawable)}\n   *\n   * @param resourceId The id of the resource to use as a placeholder\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T placeholder(@DrawableRes int resourceId) {\n    if (isAutoCloneEnabled) {\n      return clone().placeholder(resourceId);\n    }\n\n    this.placeholderId = resourceId;\n    fields |= PLACEHOLDER_ID;\n\n    placeholderDrawable = null;\n    fields &= ~PLACEHOLDER;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets an {@link Drawable} to display if the model provided to {@link\n   * com.bumptech.glide.RequestBuilder#load(Object)} is {@code null}.\n   *\n   * <p>If a fallback is not set, null models will cause the error drawable to be displayed. If the\n   * error drawable is not set, the placeholder will be displayed.\n   *\n   * <p>Replaces any previous calls to this method or {@link #fallback(int)}.\n   *\n   * @see #placeholder(Drawable)\n   * @see #placeholder(int)\n   * @param drawable The drawable to display as a placeholder.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T fallback(@Nullable Drawable drawable) {\n    if (isAutoCloneEnabled) {\n      return clone().fallback(drawable);\n    }\n\n    this.fallbackDrawable = drawable;\n    fields |= FALLBACK;\n\n    fallbackId = 0;\n    fields &= ~FALLBACK_ID;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets a resource to display if the model provided to {@link\n   * com.bumptech.glide.RequestBuilder#load(Object)} is {@code null}.\n   *\n   * <p>If a fallback is not set, null models will cause the error drawable to be displayed. If the\n   * error drawable is not set, the placeholder will be displayed.\n   *\n   * <p>Replaces any previous calls to this method or {@link #fallback(Drawable)}.\n   *\n   * @see #placeholder(Drawable)\n   * @see #placeholder(int)\n   * @param resourceId The id of the resource to use as a fallback.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T fallback(@DrawableRes int resourceId) {\n    if (isAutoCloneEnabled) {\n      return clone().fallback(resourceId);\n    }\n\n    this.fallbackId = resourceId;\n    fields |= FALLBACK_ID;\n\n    fallbackDrawable = null;\n    fields &= ~FALLBACK;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets a {@link Drawable} to display if a load fails.\n   *\n   * <p>Replaces any previous calls to this method or {@link #error(int)}\n   *\n   * @param drawable The drawable to display.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T error(@Nullable Drawable drawable) {\n    if (isAutoCloneEnabled) {\n      return clone().error(drawable);\n    }\n\n    this.errorPlaceholder = drawable;\n    fields |= ERROR_PLACEHOLDER;\n\n    this.errorId = 0;\n    fields &= ~ERROR_ID;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets a resource to display if a load fails.\n   *\n   * <p>Replaces any previous calls to this method or {@link #error(Drawable)}\n   *\n   * @param resourceId The id of the resource to use as a placeholder.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T error(@DrawableRes int resourceId) {\n    if (isAutoCloneEnabled) {\n      return clone().error(resourceId);\n    }\n    this.errorId = resourceId;\n    fields |= ERROR_ID;\n\n    this.errorPlaceholder = null;\n    fields &= ~ERROR_PLACEHOLDER;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Sets the {@link android.content.res.Resources.Theme} to apply when loading {@link Drawable}s\n   * for resource ids, including those provided via {@link #error(int)}, {@link #placeholder(int)},\n   * and {@link #fallback(Drawable)}.\n   *\n   * <p>The {@link android.content.res.Resources.Theme} provided here will override the {@link\n   * android.content.res.Resources.Theme} of the application {@link android.content.Context}.\n   *\n   * @param theme The theme to use when loading Drawables.\n   * @return this request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T theme(@Nullable Resources.Theme theme) {\n    if (isAutoCloneEnabled) {\n      return clone().theme(theme);\n    }\n    this.theme = theme;\n    if (theme != null) {\n      fields |= THEME;\n      return set(ResourceDrawableDecoder.THEME, theme);\n    } else {\n      fields &= ~THEME;\n      return removeOption(ResourceDrawableDecoder.THEME);\n    }\n  }\n\n  /**\n   * Allows the loaded resource to skip the memory cache.\n   *\n   * <p>Note - this is not a guarantee. If a request is already pending for this resource and that\n   * request is not also skipping the memory cache, the resource will be cached in memory.\n   *\n   * @param skip True to allow the resource to skip the memory cache.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T skipMemoryCache(boolean skip) {\n    if (isAutoCloneEnabled) {\n      return clone().skipMemoryCache(true);\n    }\n\n    this.isCacheable = !skip;\n    fields |= IS_CACHEABLE;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Overrides the {@link com.bumptech.glide.request.target.Target}'s width and height with the\n   * given values. This is useful for thumbnails, and should only be used for other cases when you\n   * need a very specific image size.\n   *\n   * @param width The width in pixels to use to load the resource.\n   * @param height The height in pixels to use to load the resource.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T override(int width, int height) {\n    if (isAutoCloneEnabled) {\n      return clone().override(width, height);\n    }\n\n    this.overrideWidth = width;\n    this.overrideHeight = height;\n    fields |= OVERRIDE;\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Overrides the {@link com.bumptech.glide.request.target.Target}'s width and height with the\n   * given size.\n   *\n   * @see #override(int, int)\n   * @param size The width and height to use.\n   * @return This request builder.\n   */\n  @NonNull\n  @CheckResult\n  public T override(int size) {\n    return override(size, size);\n  }\n\n  /**\n   * Sets some additional data to be mixed in to the memory and disk cache keys allowing the caller\n   * more control over when cached data is invalidated.\n   *\n   * <p>Note - The signature does not replace the cache key, it is purely additive.\n   *\n   * @param signature A unique non-null {@link Key} representing the current state of the model that\n   *     will be mixed in to the cache key.\n   * @return This request builder.\n   * @see com.bumptech.glide.signature.ObjectKey\n   */\n  @NonNull\n  @CheckResult\n  public T signature(@NonNull Key signature) {\n    if (isAutoCloneEnabled) {\n      return clone().signature(signature);\n    }\n\n    this.signature = Preconditions.checkNotNull(signature);\n    fields |= SIGNATURE;\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Returns a copy of this request builder with all of the options put so far on this builder.\n   *\n   * <p>This method returns a \"deep\" copy in that all non-immutable arguments are copied such that\n   * changes to one builder will not affect the other builder. However, in addition to immutable\n   * arguments, the current model is not copied copied so changes to the model will affect both\n   * builders.\n   *\n   * <p>Even if this object was locked, the cloned object returned from this method will not be\n   * locked.\n   */\n  @SuppressWarnings({\n    \"unchecked\",\n    // we don't want to throw to be user friendly\n    \"PMD.CloneThrowsCloneNotSupportedException\",\n    // The types we're using here do this automatically.\n    \"PMD.CloneMethodReturnTypeMustMatchClassName\"\n  })\n  @CheckResult\n  @Override\n  public T clone() {\n    try {\n      BaseRequestOptions<?> result = (BaseRequestOptions<?>) super.clone();\n      result.options = new Options();\n      result.options.putAll(options);\n      result.transformations = new CachedHashCodeArrayMap<>();\n      result.transformations.putAll(transformations);\n      result.isLocked = false;\n      result.isAutoCloneEnabled = false;\n      return (T) result;\n    } catch (CloneNotSupportedException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @NonNull\n  @CheckResult\n  public <Y> T set(@NonNull Option<Y> option, @NonNull Y value) {\n    if (isAutoCloneEnabled) {\n      return clone().set(option, value);\n    }\n\n    Preconditions.checkNotNull(option);\n    Preconditions.checkNotNull(value);\n    options.set(option, value);\n    return selfOrThrowIfLocked();\n  }\n\n  T removeOption(@NonNull Option<?> option) {\n    if (isAutoCloneEnabled) {\n      return clone().removeOption(option);\n    }\n    options.remove(option);\n    return selfOrThrowIfLocked();\n  }\n\n  @NonNull\n  @CheckResult\n  public T decode(@NonNull Class<?> resourceClass) {\n    if (isAutoCloneEnabled) {\n      return clone().decode(resourceClass);\n    }\n\n    this.resourceClass = Preconditions.checkNotNull(resourceClass);\n    fields |= RESOURCE_CLASS;\n    return selfOrThrowIfLocked();\n  }\n\n  public final boolean isTransformationAllowed() {\n    return isTransformationAllowed;\n  }\n\n  public final boolean isTransformationSet() {\n    return isSet(TRANSFORMATION);\n  }\n\n  public final boolean isLocked() {\n    return isLocked;\n  }\n\n  /**\n   * Sets the value for key {@link\n   * com.bumptech.glide.load.resource.bitmap.BitmapEncoder#COMPRESSION_FORMAT}.\n   */\n  @NonNull\n  @CheckResult\n  public T encodeFormat(@NonNull Bitmap.CompressFormat format) {\n    return set(BitmapEncoder.COMPRESSION_FORMAT, Preconditions.checkNotNull(format));\n  }\n\n  /** Sets the value for key {@link BitmapEncoder#COMPRESSION_QUALITY}. */\n  @NonNull\n  @CheckResult\n  public T encodeQuality(@IntRange(from = 0, to = 100) int quality) {\n    return set(BitmapEncoder.COMPRESSION_QUALITY, quality);\n  }\n\n  /**\n   * Sets the time position of the frame to extract from a video.\n   *\n   * <p>This is a component option specific to {@link VideoDecoder}. If the default video decoder is\n   * replaced or skipped because of your configuration, this option may be ignored.\n   *\n   * @see VideoDecoder#TARGET_FRAME\n   * @param frameTimeMicros The time position in microseconds of the desired frame. If negative, the\n   *     Android framework implementation return a representative frame.\n   */\n  @NonNull\n  @CheckResult\n  public T frame(@IntRange(from = 0) long frameTimeMicros) {\n    return set(VideoDecoder.TARGET_FRAME, frameTimeMicros);\n  }\n\n  /**\n   * Sets the {@link DecodeFormat} to use when decoding {@link Bitmap} objects using {@link\n   * Downsampler} and Glide's default GIF decoders.\n   *\n   * <p>{@link DecodeFormat} is a request, not a requirement. It's possible the resource will be\n   * decoded using a decoder that cannot control the format ({@link\n   * android.media.MediaMetadataRetriever} for example), or that the decoder may choose to ignore\n   * the requested format if it can't display the image (i.e. RGB_565 is requested, but the image\n   * has alpha).\n   *\n   * <p>This is a component option specific to {@link Downsampler} and Glide's GIF decoders. If the\n   * default Bitmap decoders are replaced or skipped because of your configuration, this option may\n   * be ignored.\n   *\n   * <p>To set only the format used when decoding {@link Bitmap}s, use {@link #set(Option, Object)}}\n   * and {@link Downsampler#DECODE_FORMAT}. To set only the format used when decoding GIF frames,\n   * use {@link #set(Option, Object)} and {@link GifOptions#DECODE_FORMAT}.\n   *\n   * @see Downsampler#DECODE_FORMAT\n   * @see GifOptions#DECODE_FORMAT\n   */\n  @NonNull\n  @CheckResult\n  public T format(@NonNull DecodeFormat format) {\n    Preconditions.checkNotNull(format);\n    return set(Downsampler.DECODE_FORMAT, format).set(GifOptions.DECODE_FORMAT, format);\n  }\n\n  /**\n   * Disables the use of {@link android.graphics.Bitmap.Config#HARDWARE} in {@link Downsampler} to\n   * avoid errors caused by inspecting Bitmap pixels, drawing with hardware support disabled,\n   * drawing to {@link android.graphics.Canvas}s backed by {@link Bitmap}s etc.\n   *\n   * <p>It's almost never safe to set {@link Downsampler#ALLOW_HARDWARE_CONFIG} to {@code true} so\n   * we only provide a way to disable hardware configs entirely. If no option is set for {@link\n   * Downsampler#ALLOW_HARDWARE_CONFIG}, Glide will set the value per request based on whether or\n   * not a {@link Transformation} is applied and if one is, the type of {@link Transformation}\n   * applied. Built in transformations like {@link FitCenter} and {@link\n   * com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.CenterOutside} can safely use {@link\n   * android.graphics.Bitmap.Config#HARDWARE} because they can be entirely replaced by scaling\n   * within {@link Downsampler}. {@link Transformation}s like {@link #circleCrop()} that can't be\n   * replicated by {@link Downsampler} cannot use {@link Bitmap.Config#HARDWARE} because {@link\n   * android.graphics.Bitmap.Config#HARDWARE} cannot be drawn to {@link android.graphics.Canvas}s,\n   * which is required by most {@link Transformation}s.\n   */\n  @NonNull\n  @CheckResult\n  public T disallowHardwareConfig() {\n    return set(Downsampler.ALLOW_HARDWARE_CONFIG, false);\n  }\n\n  /**\n   * Sets the {@link DownsampleStrategy} to use when decoding {@link Bitmap Bitmaps} using {@link\n   * Downsampler}.\n   *\n   * <p>This is a component option specific to {@link Downsampler}. If the defautlt Bitmap decoder\n   * is replaced or skipped because of your configuration, this option may be ignored.\n   */\n  @NonNull\n  @CheckResult\n  public T downsample(@NonNull DownsampleStrategy strategy) {\n    return set(DownsampleStrategy.OPTION, Preconditions.checkNotNull(strategy));\n  }\n\n  /**\n   * Sets the read and write timeout for the http requests used to load the image.\n   *\n   * <p>This is a component option specific to Glide's default networking library and {@link\n   * com.bumptech.glide.load.model.stream.HttpGlideUrlLoader}. If you use any other networking\n   * library including Glide's Volley or OkHttp integration libraries, this option will be ignored.\n   *\n   * @see com.bumptech.glide.load.model.stream.HttpGlideUrlLoader#TIMEOUT\n   * @param timeoutMs The read and write timeout in milliseconds.\n   */\n  @NonNull\n  @CheckResult\n  public T timeout(@IntRange(from = 0) int timeoutMs) {\n    return set(HttpGlideUrlLoader.TIMEOUT, timeoutMs);\n  }\n\n  /**\n   * Applies {@link com.bumptech.glide.load.resource.bitmap.CenterCrop} to all default types, and\n   * ignores unknown types.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @see #optionalTransform(Class, Transformation)\n   * @see #centerCrop()\n   */\n  @NonNull\n  @CheckResult\n  public T optionalCenterCrop() {\n    return optionalTransform(DownsampleStrategy.CENTER_OUTSIDE, new CenterCrop());\n  }\n\n  /**\n   * Applies {@link CenterCrop} to all default types and throws an exception if asked to transform\n   * an unknown type.\n   *\n   * <p>this will override previous calls to {@link #dontTransform()} ()}.\n   *\n   * @see #transform(Class, Transformation)\n   * @see #optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public T centerCrop() {\n    return transform(DownsampleStrategy.CENTER_OUTSIDE, new CenterCrop());\n  }\n\n  /**\n   * Applies {@link FitCenter} and to all default types, {@link DownsampleStrategy#FIT_CENTER} to\n   * image types, and ignores unknown types.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()} and previous calls to {@link\n   * #downsample(DownsampleStrategy)}.\n   *\n   * @see #optionalTransform(Class, Transformation)\n   * @see #fitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public T optionalFitCenter() {\n    return optionalScaleOnlyTransform(DownsampleStrategy.FIT_CENTER, new FitCenter());\n  }\n\n  /**\n   * Applies {@link FitCenter} and to all default types, {@link DownsampleStrategy#FIT_CENTER} to\n   * image types, and throws an exception if asked to transform an unknown type.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()} and previous calls to {@link\n   * #downsample(DownsampleStrategy)}.\n   *\n   * @see #transform(Class, Transformation)\n   * @see #optionalFitCenter()\n   */\n  @NonNull\n  @CheckResult\n  public T fitCenter() {\n    return scaleOnlyTransform(DownsampleStrategy.FIT_CENTER, new FitCenter());\n  }\n\n  /**\n   * Applies {@link com.bumptech.glide.load.resource.bitmap.CenterInside} to all default types,\n   * {@link DownsampleStrategy#CENTER_INSIDE} to image types, and ignores unknown types.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()} and previous calls to {@link\n   * #downsample(DownsampleStrategy)}.\n   *\n   * @see #optionalTransform(Class, Transformation)\n   * @see #centerInside()\n   */\n  @NonNull\n  @CheckResult\n  public T optionalCenterInside() {\n    return optionalScaleOnlyTransform(DownsampleStrategy.CENTER_INSIDE, new CenterInside());\n  }\n\n  /**\n   * Applies {@link CenterInside} to all default types, {@link DownsampleStrategy#CENTER_INSIDE} to\n   * image types and throws an exception if asked to transform an unknown type.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()} and previous calls to {@link\n   * #downsample(DownsampleStrategy)}.\n   *\n   * @see #transform(Class, Transformation)\n   * @see #optionalCenterInside()\n   */\n  @NonNull\n  @CheckResult\n  public T centerInside() {\n    return scaleOnlyTransform(DownsampleStrategy.CENTER_INSIDE, new CenterInside());\n  }\n\n  /**\n   * Applies {@link CircleCrop} to all default types, and ignores unknown types.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @see #optionalTransform(Transformation)\n   * @see #circleCrop()\n   */\n  @NonNull\n  @CheckResult\n  public T optionalCircleCrop() {\n    return optionalTransform(DownsampleStrategy.CENTER_OUTSIDE, new CircleCrop());\n  }\n\n  /**\n   * Applies {@link CircleCrop} to all default types and throws an exception if asked to transform\n   * an unknown type.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @see #transform(Class, Transformation)\n   * @see #optionalCenterCrop()\n   */\n  @NonNull\n  @CheckResult\n  public T circleCrop() {\n    return transform(DownsampleStrategy.CENTER_INSIDE, new CircleCrop());\n  }\n\n  // calling optionalTransform() on the result of clone() requires greater access.\n  // calling downsample is guaranteed to modify the current object by the isAutoCloneEnabledCheck.\n  @SuppressWarnings({\"WeakerAccess\", \"CheckResult\"})\n  @NonNull\n  final T optionalTransform(\n      @NonNull DownsampleStrategy downsampleStrategy,\n      @NonNull Transformation<Bitmap> transformation) {\n    if (isAutoCloneEnabled) {\n      return clone().optionalTransform(downsampleStrategy, transformation);\n    }\n\n    downsample(downsampleStrategy);\n    return transform(transformation, /* isRequired= */ false);\n  }\n\n  // calling transform() on the result of clone() requires greater access.\n  // calling downsample is guaranteed to modify the current object by the isAutoCloneEnabledCheck.\n  @SuppressWarnings({\"WeakerAccess\", \"CheckResult\"})\n  @NonNull\n  @CheckResult\n  final T transform(\n      @NonNull DownsampleStrategy downsampleStrategy,\n      @NonNull Transformation<Bitmap> transformation) {\n    if (isAutoCloneEnabled) {\n      return clone().transform(downsampleStrategy, transformation);\n    }\n\n    downsample(downsampleStrategy);\n    return transform(transformation);\n  }\n\n  @NonNull\n  private T scaleOnlyTransform(\n      @NonNull DownsampleStrategy strategy, @NonNull Transformation<Bitmap> transformation) {\n    return scaleOnlyTransform(strategy, transformation, true /*isTransformationRequired*/);\n  }\n\n  @NonNull\n  private T optionalScaleOnlyTransform(\n      @NonNull DownsampleStrategy strategy, @NonNull Transformation<Bitmap> transformation) {\n    return scaleOnlyTransform(strategy, transformation, false /*isTransformationRequired*/);\n  }\n\n  // We know that result will always be T since we created result.\n  @SuppressWarnings(\"unchecked\")\n  @NonNull\n  private T scaleOnlyTransform(\n      @NonNull DownsampleStrategy strategy,\n      @NonNull Transformation<Bitmap> transformation,\n      boolean isTransformationRequired) {\n    BaseRequestOptions<T> result =\n        isTransformationRequired\n            ? transform(strategy, transformation)\n            : optionalTransform(strategy, transformation);\n    result.isScaleOnlyOrNoTransform = true;\n    return (T) result;\n  }\n\n  /**\n   * Applies the given {@link Transformation} for {@link Bitmap Bitmaps} to the default types\n   * ({@link Bitmap}, {@link android.graphics.drawable.BitmapDrawable}, and {@link\n   * com.bumptech.glide.load.resource.gif.GifDrawable}) and throws an exception if asked to\n   * transform an unknown type.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @param transformation Any {@link Transformation} for {@link Bitmap}s.\n   * @see #optionalTransform(Transformation)\n   * @see #optionalTransform(Class, Transformation)\n   */\n  // Guaranteed to modify the current object by the isAutoCloneEnabledCheck.\n  @SuppressWarnings(\"CheckResult\")\n  @NonNull\n  @CheckResult\n  public T transform(@NonNull Transformation<Bitmap> transformation) {\n    return transform(transformation, /* isRequired= */ true);\n  }\n\n  /**\n   * Applies the given {@link Transformation}s in the given order for {@link Bitmap Bitmaps} to the\n   * default types ({@link Bitmap}, {@link android.graphics.drawable.BitmapDrawable}, and {@link\n   * com.bumptech.glide.load.resource.gif.GifDrawable}) and throws an exception if asked to\n   * transform an unknown type.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @param transformations One or more {@link Transformation}s for {@link Bitmap}s.\n   * @see #optionalTransform(Transformation)\n   * @see #optionalTransform(Class, Transformation)\n   */\n  // Guaranteed to modify the current object by the isAutoCloneEnabledCheck.\n  @SuppressWarnings({\"unchecked\", \"varargs\", \"CheckResult\"})\n  @NonNull\n  @CheckResult\n  public T transform(@NonNull Transformation<Bitmap>... transformations) {\n    if (transformations.length > 1) {\n      return transform(new MultiTransformation<>(transformations), /* isRequired= */ true);\n    } else if (transformations.length == 1) {\n      return transform(transformations[0]);\n    } else {\n      return selfOrThrowIfLocked();\n    }\n  }\n\n  /**\n   * Applies the given {@link Transformation}s in the given order for {@link Bitmap Bitmaps} to the\n   * default types ({@link Bitmap}, {@link android.graphics.drawable.BitmapDrawable}, and {@link\n   * com.bumptech.glide.load.resource.gif.GifDrawable}) and throws an exception if asked to\n   * transform an unknown type.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @deprecated Deprecated due to api update, use {@link #transform(Transformation[])} instead\n   * @param transformations One or more {@link Transformation}s for {@link Bitmap}s.\n   * @see #optionalTransform(Transformation)\n   * @see #optionalTransform(Class, Transformation)\n   */\n  // Guaranteed to modify the current object by the isAutoCloneEnabledCheck.\n  @SuppressWarnings({\"unchecked\", \"varargs\", \"CheckResult\"})\n  @NonNull\n  @CheckResult\n  @Deprecated\n  public T transforms(@NonNull Transformation<Bitmap>... transformations) {\n    return transform(new MultiTransformation<>(transformations), /* isRequired= */ true);\n  }\n\n  /**\n   * Applies the given {@link Transformation} for {@link Bitmap Bitmaps} to the default types\n   * ({@link Bitmap}, {@link android.graphics.drawable.BitmapDrawable}, and {@link\n   * com.bumptech.glide.load.resource.gif.GifDrawable}) and ignores unknown types.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @param transformation Any {@link Transformation} for {@link Bitmap}s.\n   * @see #transform(Transformation)\n   * @see #transform(Class, Transformation)\n   */\n  // Guaranteed to modify the current object by the isAutoCloneEnabledCheck.\n  @SuppressWarnings(\"CheckResult\")\n  @NonNull\n  @CheckResult\n  public T optionalTransform(@NonNull Transformation<Bitmap> transformation) {\n    return transform(transformation, /* isRequired= */ false);\n  }\n\n  @NonNull\n  T transform(@NonNull Transformation<Bitmap> transformation, boolean isRequired) {\n    if (isAutoCloneEnabled) {\n      return clone().transform(transformation, isRequired);\n    }\n\n    DrawableTransformation drawableTransformation =\n        new DrawableTransformation(transformation, isRequired);\n    transform(Bitmap.class, transformation, isRequired);\n    transform(Drawable.class, drawableTransformation, isRequired);\n    // TODO: remove BitmapDrawable decoder and this transformation.\n    // Registering as BitmapDrawable is simply an optimization to avoid some iteration and\n    // isAssignableFrom checks when obtaining the transformation later on. It can be removed without\n    // affecting the functionality.\n    transform(BitmapDrawable.class, drawableTransformation.asBitmapDrawable(), isRequired);\n    transform(GifDrawable.class, new GifDrawableTransformation(transformation), isRequired);\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Applies the given {@link Transformation} for any decoded resource of the given type and allows\n   * unknown resource types to be ignored.\n   *\n   * <p>Users can apply different transformations for each resource class. Applying a {@link\n   * Transformation} for a resource type that already has a {@link Transformation} will override the\n   * previous call.\n   *\n   * <p>If any calls are made to the non-optional transform methods, then attempting to transform an\n   * unknown resource class will throw an exception. To allow unknown types, users must always call\n   * the optional version of each method.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @param resourceClass The type of resource to transform.\n   * @param transformation The {@link Transformation} to apply.\n   */\n  @NonNull\n  @CheckResult\n  public <Y> T optionalTransform(\n      @NonNull Class<Y> resourceClass, @NonNull Transformation<Y> transformation) {\n    return transform(resourceClass, transformation, /* isRequired= */ false);\n  }\n\n  @NonNull\n  <Y> T transform(\n      @NonNull Class<Y> resourceClass,\n      @NonNull Transformation<Y> transformation,\n      boolean isRequired) {\n    if (isAutoCloneEnabled) {\n      return clone().transform(resourceClass, transformation, isRequired);\n    }\n\n    Preconditions.checkNotNull(resourceClass);\n    Preconditions.checkNotNull(transformation);\n    transformations.put(resourceClass, transformation);\n    fields |= TRANSFORMATION;\n    isTransformationAllowed = true;\n    fields |= TRANSFORMATION_ALLOWED;\n    // Always set to false here. Known scale only transformations will call this method and then\n    // set isScaleOnlyOrNoTransform to true immediately after.\n    isScaleOnlyOrNoTransform = false;\n    if (isRequired) {\n      fields |= TRANSFORMATION_REQUIRED;\n      isTransformationRequired = true;\n    }\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Applies the given {@link Transformation} for any decoded resource of the given type and throws\n   * if asked to transform an unknown resource type.\n   *\n   * <p>This will override previous calls to {@link #dontTransform()}.\n   *\n   * @param resourceClass The type of resource to transform.\n   * @param transformation The {@link Transformation} to apply.\n   * @see #optionalTransform(Class, Transformation)\n   */\n  // Guaranteed to modify the current object by the isAutoCloneEnabledCheck.\n  @SuppressWarnings(\"CheckResult\")\n  @NonNull\n  @CheckResult\n  public <Y> T transform(\n      @NonNull Class<Y> resourceClass, @NonNull Transformation<Y> transformation) {\n    return transform(resourceClass, transformation, /* isRequired= */ true);\n  }\n\n  /**\n   * Removes all applied {@link Transformation Transformations} for all resource classes and allows\n   * unknown resource types to be transformed without throwing an exception.\n   */\n  @NonNull\n  @CheckResult\n  public T dontTransform() {\n    if (isAutoCloneEnabled) {\n      return clone().dontTransform();\n    }\n\n    transformations.clear();\n    fields &= ~TRANSFORMATION;\n    isTransformationRequired = false;\n    fields &= ~TRANSFORMATION_REQUIRED;\n    isTransformationAllowed = false;\n    fields |= TRANSFORMATION_ALLOWED;\n    isScaleOnlyOrNoTransform = true;\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Disables resource decoders that return animated resources so any resource returned will be\n   * static.\n   *\n   * <p>To disable transitions (fades etc) use {@link\n   * com.bumptech.glide.TransitionOptions#dontTransition()}\n   */\n  // Guaranteed to modify the current object by the isAutoCloneEnabledCheck.\n  @SuppressWarnings(\"CheckResult\")\n  @NonNull\n  @CheckResult\n  public T dontAnimate() {\n    return set(GifOptions.DISABLE_ANIMATION, true);\n  }\n\n  /**\n   * Updates this options set with any options that are explicitly set in the given {@code T} object\n   * and returns this object if {@link #autoClone()} is disabled or a new {@code T} object if {@link\n   * #autoClone()} is enabled.\n   *\n   * <p>{@code #apply} only replaces those values that are explicitly set in the given {@code T}. If\n   * you need to completely reset all previously set options, create a new {@code T} object instead\n   * of using this method.\n   *\n   * <p>The options that will be set to values in the returned {@code T} object is the intersection\n   * of the set of options in this {@code T} object and the given {@code T} object that were\n   * explicitly set. If the values of any of the options conflict, the values in the returned {@code\n   * T} object will be set to those in the given {@code T} object.\n   */\n  @NonNull\n  @CheckResult\n  public T apply(@NonNull BaseRequestOptions<?> o) {\n    if (isAutoCloneEnabled) {\n      return clone().apply(o);\n    }\n    BaseRequestOptions<?> other = o;\n\n    if (isSet(other.fields, SIZE_MULTIPLIER)) {\n      sizeMultiplier = other.sizeMultiplier;\n    }\n    if (isSet(other.fields, USE_UNLIMITED_SOURCE_GENERATORS_POOL)) {\n      useUnlimitedSourceGeneratorsPool = other.useUnlimitedSourceGeneratorsPool;\n    }\n    if (isSet(other.fields, USE_ANIMATION_POOL)) {\n      useAnimationPool = other.useAnimationPool;\n    }\n    if (isSet(other.fields, DISK_CACHE_STRATEGY)) {\n      diskCacheStrategy = other.diskCacheStrategy;\n    }\n    if (isSet(other.fields, PRIORITY)) {\n      priority = other.priority;\n    }\n    if (isSet(other.fields, ERROR_PLACEHOLDER)) {\n      errorPlaceholder = other.errorPlaceholder;\n      errorId = 0;\n      fields &= ~ERROR_ID;\n    }\n    if (isSet(other.fields, ERROR_ID)) {\n      errorId = other.errorId;\n      errorPlaceholder = null;\n      fields &= ~ERROR_PLACEHOLDER;\n    }\n    if (isSet(other.fields, PLACEHOLDER)) {\n      placeholderDrawable = other.placeholderDrawable;\n      placeholderId = 0;\n      fields &= ~PLACEHOLDER_ID;\n    }\n    if (isSet(other.fields, PLACEHOLDER_ID)) {\n      placeholderId = other.placeholderId;\n      placeholderDrawable = null;\n      fields &= ~PLACEHOLDER;\n    }\n    if (isSet(other.fields, IS_CACHEABLE)) {\n      isCacheable = other.isCacheable;\n    }\n    if (isSet(other.fields, OVERRIDE)) {\n      overrideWidth = other.overrideWidth;\n      overrideHeight = other.overrideHeight;\n    }\n    if (isSet(other.fields, SIGNATURE)) {\n      signature = other.signature;\n    }\n    if (isSet(other.fields, RESOURCE_CLASS)) {\n      resourceClass = other.resourceClass;\n    }\n    if (isSet(other.fields, FALLBACK)) {\n      fallbackDrawable = other.fallbackDrawable;\n      fallbackId = 0;\n      fields &= ~FALLBACK_ID;\n    }\n    if (isSet(other.fields, FALLBACK_ID)) {\n      fallbackId = other.fallbackId;\n      fallbackDrawable = null;\n      fields &= ~FALLBACK;\n    }\n    if (isSet(other.fields, THEME)) {\n      theme = other.theme;\n    }\n    if (isSet(other.fields, TRANSFORMATION_ALLOWED)) {\n      isTransformationAllowed = other.isTransformationAllowed;\n    }\n    if (isSet(other.fields, TRANSFORMATION_REQUIRED)) {\n      isTransformationRequired = other.isTransformationRequired;\n    }\n    if (isSet(other.fields, TRANSFORMATION)) {\n      transformations.putAll(other.transformations);\n      isScaleOnlyOrNoTransform = other.isScaleOnlyOrNoTransform;\n    }\n    if (isSet(other.fields, ONLY_RETRIEVE_FROM_CACHE)) {\n      onlyRetrieveFromCache = other.onlyRetrieveFromCache;\n    }\n\n    // Applying options with dontTransform() is expected to clear our transformations.\n    if (!isTransformationAllowed) {\n      transformations.clear();\n      fields &= ~TRANSFORMATION;\n      isTransformationRequired = false;\n      fields &= ~TRANSFORMATION_REQUIRED;\n      isScaleOnlyOrNoTransform = true;\n    }\n\n    fields |= other.fields;\n    options.putAll(other.options);\n\n    return selfOrThrowIfLocked();\n  }\n\n  /**\n   * Returns {@code true} if this {@link BaseRequestOptions} is equivalent to the given {@link\n   * BaseRequestOptions} (has all of the same options and sizes).\n   *\n   * <p>This method is identical to {@link #equals(Object)}, but this can not be overridden. We need\n   * to use this method instead of {@link #equals(Object)}, because child classes may have\n   * additional fields, such as listeners and models, that should not be considered when checking\n   * for equality.\n   */\n  public final boolean isEquivalentTo(BaseRequestOptions<?> other) {\n    return Float.compare(other.sizeMultiplier, sizeMultiplier) == 0\n        && errorId == other.errorId\n        && Util.bothNullOrEqual(errorPlaceholder, other.errorPlaceholder)\n        && placeholderId == other.placeholderId\n        && Util.bothNullOrEqual(placeholderDrawable, other.placeholderDrawable)\n        && fallbackId == other.fallbackId\n        && Util.bothNullOrEqual(fallbackDrawable, other.fallbackDrawable)\n        && isCacheable == other.isCacheable\n        && overrideHeight == other.overrideHeight\n        && overrideWidth == other.overrideWidth\n        && isTransformationRequired == other.isTransformationRequired\n        && isTransformationAllowed == other.isTransformationAllowed\n        && useUnlimitedSourceGeneratorsPool == other.useUnlimitedSourceGeneratorsPool\n        && onlyRetrieveFromCache == other.onlyRetrieveFromCache\n        && diskCacheStrategy.equals(other.diskCacheStrategy)\n        && priority == other.priority\n        && options.equals(other.options)\n        && transformations.equals(other.transformations)\n        && resourceClass.equals(other.resourceClass)\n        && Util.bothNullOrEqual(signature, other.signature)\n        && Util.bothNullOrEqual(theme, other.theme);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof BaseRequestOptions<?>) {\n      return isEquivalentTo((BaseRequestOptions<?>) o);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    int hashCode = Util.hashCode(sizeMultiplier);\n    hashCode = Util.hashCode(errorId, hashCode);\n    hashCode = Util.hashCode(errorPlaceholder, hashCode);\n    hashCode = Util.hashCode(placeholderId, hashCode);\n    hashCode = Util.hashCode(placeholderDrawable, hashCode);\n    hashCode = Util.hashCode(fallbackId, hashCode);\n    hashCode = Util.hashCode(fallbackDrawable, hashCode);\n    hashCode = Util.hashCode(isCacheable, hashCode);\n    hashCode = Util.hashCode(overrideHeight, hashCode);\n    hashCode = Util.hashCode(overrideWidth, hashCode);\n    hashCode = Util.hashCode(isTransformationRequired, hashCode);\n    hashCode = Util.hashCode(isTransformationAllowed, hashCode);\n    hashCode = Util.hashCode(useUnlimitedSourceGeneratorsPool, hashCode);\n    hashCode = Util.hashCode(onlyRetrieveFromCache, hashCode);\n    hashCode = Util.hashCode(diskCacheStrategy, hashCode);\n    hashCode = Util.hashCode(priority, hashCode);\n    hashCode = Util.hashCode(options, hashCode);\n    hashCode = Util.hashCode(transformations, hashCode);\n    hashCode = Util.hashCode(resourceClass, hashCode);\n    hashCode = Util.hashCode(signature, hashCode);\n    hashCode = Util.hashCode(theme, hashCode);\n    return hashCode;\n  }\n\n  /**\n   * Throws if any further mutations are attempted.\n   *\n   * <p>Once locked, the only way to unlock is to use {@link #clone()}\n   */\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  public T lock() {\n    isLocked = true;\n    // This is the only place we should not check locked.\n    return self();\n  }\n\n  /**\n   * Similar to {@link #lock()} except that mutations cause a {@link #clone()} operation to happen\n   * before the mutation resulting in all methods returning a new Object and leaving the original\n   * locked object unmodified.\n   *\n   * <p>Auto clone is not retained by cloned objects returned from mutations. The cloned objects are\n   * mutable and are not locked.\n   */\n  @NonNull\n  public T autoClone() {\n    if (isLocked && !isAutoCloneEnabled) {\n      throw new IllegalStateException(\n          \"You cannot auto lock an already locked options object\" + \", try clone() first\");\n    }\n    isAutoCloneEnabled = true;\n    return lock();\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  protected final T selfOrThrowIfLocked() {\n    if (isLocked) {\n      throw new IllegalStateException(\"You cannot modify locked T, consider clone()\");\n    }\n    return self();\n  }\n\n  protected final boolean isAutoCloneEnabled() {\n    return isAutoCloneEnabled;\n  }\n\n  public final boolean isDiskCacheStrategySet() {\n    return isSet(DISK_CACHE_STRATEGY);\n  }\n\n  public final boolean isSkipMemoryCacheSet() {\n    return isSet(IS_CACHEABLE);\n  }\n\n  @NonNull\n  public final Map<Class<?>, Transformation<?>> getTransformations() {\n    return transformations;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  public final boolean isTransformationRequired() {\n    return isTransformationRequired;\n  }\n\n  @NonNull\n  public final Options getOptions() {\n    return options;\n  }\n\n  @NonNull\n  public final Class<?> getResourceClass() {\n    return resourceClass;\n  }\n\n  @NonNull\n  public final DiskCacheStrategy getDiskCacheStrategy() {\n    return diskCacheStrategy;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Nullable\n  public final Drawable getErrorPlaceholder() {\n    return errorPlaceholder;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  public final int getErrorId() {\n    return errorId;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  public final int getPlaceholderId() {\n    return placeholderId;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Nullable\n  public final Drawable getPlaceholderDrawable() {\n    return placeholderDrawable;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  public final int getFallbackId() {\n    return fallbackId;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Nullable\n  public final Drawable getFallbackDrawable() {\n    return fallbackDrawable;\n  }\n\n  @Nullable\n  public final Resources.Theme getTheme() {\n    return theme;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  public final boolean isMemoryCacheable() {\n    return isCacheable;\n  }\n\n  @NonNull\n  public final Key getSignature() {\n    return signature;\n  }\n\n  public final boolean isPrioritySet() {\n    return isSet(PRIORITY);\n  }\n\n  @NonNull\n  public final Priority getPriority() {\n    return priority;\n  }\n\n  public final int getOverrideWidth() {\n    return overrideWidth;\n  }\n\n  public final boolean isValidOverride() {\n    return Util.isValidDimensions(overrideWidth, overrideHeight);\n  }\n\n  public final int getOverrideHeight() {\n    return overrideHeight;\n  }\n\n  public final float getSizeMultiplier() {\n    return sizeMultiplier;\n  }\n\n  boolean isScaleOnlyOrNoTransform() {\n    return isScaleOnlyOrNoTransform;\n  }\n\n  private boolean isSet(int flag) {\n    return isSet(fields, flag);\n  }\n\n  // get is just as clear.\n  @SuppressWarnings(\"PMD.BooleanGetMethodName\")\n  public final boolean getUseUnlimitedSourceGeneratorsPool() {\n    return useUnlimitedSourceGeneratorsPool;\n  }\n\n  // get is just as clear.\n  @SuppressWarnings(\"PMD.BooleanGetMethodName\")\n  public final boolean getUseAnimationPool() {\n    return useAnimationPool;\n  }\n\n  // get is just as clear.\n  @SuppressWarnings(\"PMD.BooleanGetMethodName\")\n  public final boolean getOnlyRetrieveFromCache() {\n    return onlyRetrieveFromCache;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private T self() {\n    return (T) this;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/ErrorRequestCoordinator.java",
    "content": "package com.bumptech.glide.request;\n\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.Nullable;\n\n/**\n * Runs a single primary {@link Request} until it completes and then a fallback error request only\n * if the single primary request fails.\n */\npublic final class ErrorRequestCoordinator implements RequestCoordinator, Request {\n\n  private final Object requestLock;\n  @Nullable private final RequestCoordinator parent;\n\n  private volatile Request primary;\n  private volatile Request error;\n\n  @GuardedBy(\"requestLock\")\n  private RequestState primaryState = RequestState.CLEARED;\n\n  @GuardedBy(\"requestLock\")\n  private RequestState errorState = RequestState.CLEARED;\n\n  public ErrorRequestCoordinator(Object requestLock, @Nullable RequestCoordinator parent) {\n    this.requestLock = requestLock;\n    this.parent = parent;\n  }\n\n  public void setRequests(Request primary, Request error) {\n    this.primary = primary;\n    this.error = error;\n  }\n\n  @Override\n  public void begin() {\n    synchronized (requestLock) {\n      if (primaryState != RequestState.RUNNING) {\n        primaryState = RequestState.RUNNING;\n        primary.begin();\n      }\n    }\n  }\n\n  @Override\n  public void clear() {\n    synchronized (requestLock) {\n      primaryState = RequestState.CLEARED;\n      primary.clear();\n      // Don't check primary's failed state here because it will have been reset by the clear call\n      // immediately before this.\n      if (errorState != RequestState.CLEARED) {\n        errorState = RequestState.CLEARED;\n        error.clear();\n      }\n    }\n  }\n\n  @Override\n  public void pause() {\n    synchronized (requestLock) {\n      if (primaryState == RequestState.RUNNING) {\n        primaryState = RequestState.PAUSED;\n        primary.pause();\n      }\n      if (errorState == RequestState.RUNNING) {\n        errorState = RequestState.PAUSED;\n        error.pause();\n      }\n    }\n  }\n\n  @Override\n  public boolean isRunning() {\n    synchronized (requestLock) {\n      return primaryState == RequestState.RUNNING || errorState == RequestState.RUNNING;\n    }\n  }\n\n  @Override\n  public boolean isComplete() {\n    synchronized (requestLock) {\n      return primaryState == RequestState.SUCCESS || errorState == RequestState.SUCCESS;\n    }\n  }\n\n  @Override\n  public boolean isCleared() {\n    synchronized (requestLock) {\n      return primaryState == RequestState.CLEARED && errorState == RequestState.CLEARED;\n    }\n  }\n\n  @Override\n  public boolean isEquivalentTo(Request o) {\n    if (o instanceof ErrorRequestCoordinator) {\n      ErrorRequestCoordinator other = (ErrorRequestCoordinator) o;\n      return primary.isEquivalentTo(other.primary) && error.isEquivalentTo(other.error);\n    }\n    return false;\n  }\n\n  @Override\n  public boolean canSetImage(Request request) {\n    synchronized (requestLock) {\n      // Only one of primary or error runs at a time, so if we've reached this point and nothing\n      // else is broken, we should have nothing else to enforce.\n      return parentCanSetImage();\n    }\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean parentCanSetImage() {\n    return parent == null || parent.canSetImage(this);\n  }\n\n  @Override\n  public boolean canNotifyStatusChanged(Request request) {\n    synchronized (requestLock) {\n      return parentCanNotifyStatusChanged() && isValidRequestForStatusChanged(request);\n    }\n  }\n\n  @Override\n  public boolean canNotifyCleared(Request request) {\n    synchronized (requestLock) {\n      return parentCanNotifyCleared() && request.equals(primary);\n    }\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean parentCanNotifyCleared() {\n    return parent == null || parent.canNotifyCleared(this);\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean parentCanNotifyStatusChanged() {\n    return parent == null || parent.canNotifyStatusChanged(this);\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean isValidRequestForStatusChanged(Request request) {\n    if (primaryState != RequestState.FAILED) {\n      return request.equals(primary);\n    } else {\n      return request.equals(error)\n          // We don't want to call onLoadStarted once for the primary request and then again\n          // if it fails and the error request starts. It's already running, so we might as well\n          // avoid the duplicate notification by only notifying about the error state when it's\n          // final.\n          && (errorState == RequestState.SUCCESS || errorState == RequestState.FAILED);\n    }\n  }\n\n  @Override\n  public boolean isAnyResourceSet() {\n    synchronized (requestLock) {\n      return primary.isAnyResourceSet() || error.isAnyResourceSet();\n    }\n  }\n\n  @Override\n  public void onRequestSuccess(Request request) {\n    synchronized (requestLock) {\n      if (request.equals(primary)) {\n        primaryState = RequestState.SUCCESS;\n      } else if (request.equals(error)) {\n        errorState = RequestState.SUCCESS;\n      }\n      if (parent != null) {\n        parent.onRequestSuccess(this);\n      }\n    }\n  }\n\n  @Override\n  public void onRequestFailed(Request request) {\n    synchronized (requestLock) {\n      if (!request.equals(error)) {\n        primaryState = RequestState.FAILED;\n        if (errorState != RequestState.RUNNING) {\n          errorState = RequestState.RUNNING;\n          error.begin();\n        }\n        return;\n      }\n\n      errorState = RequestState.FAILED;\n\n      if (parent != null) {\n        parent.onRequestFailed(this);\n      }\n    }\n  }\n\n  @Override\n  public RequestCoordinator getRoot() {\n    synchronized (requestLock) {\n      return parent != null ? parent.getRoot() : this;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/ExperimentalRequestListener.java",
    "content": "package com.bumptech.glide.request;\n\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.request.target.Target;\n\n/**\n * An extension of {@link RequestListener} with additional parameters.\n *\n * <p>All equivalent methods are called at the relevant time by Glide. Implementations therefore\n * should only implement one version of each method.\n *\n * @param <ResourceT> The type of resource that will be loaded for the request.\n * @deprecated Not ready for public consumption, avoid using this class. It may be removed at any\n *     time.\n */\n@Deprecated\npublic abstract class ExperimentalRequestListener<ResourceT> implements RequestListener<ResourceT> {\n\n  public void onRequestStarted(Object model) {}\n\n  /**\n   * Identical to {@link #onResourceReady(Object, Object, Target, DataSource, boolean)} except that\n   * {@code isAlternateCacheKey} is provided.\n   *\n   * @param isAlternateCacheKey True if the data was obtained from the disk cache using an alternate\n   *     cache key provided by a {@link com.bumptech.glide.load.model.ModelLoader} via {@link\n   *     com.bumptech.glide.load.model.ModelLoader.LoadData#alternateKeys}. Valid only if {@code\n   *     dataSource} is {@link DataSource#DATA_DISK_CACHE} or {@link\n   *     DataSource#RESOURCE_DISK_CACHE}.\n   */\n  public abstract boolean onResourceReady(\n      ResourceT resource,\n      Object model,\n      Target<ResourceT> target,\n      DataSource dataSource,\n      boolean isFirstResource,\n      boolean isAlternateCacheKey);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/FutureTarget.java",
    "content": "package com.bumptech.glide.request;\n\nimport com.bumptech.glide.request.target.Target;\nimport java.util.concurrent.Future;\n\n/**\n * An interface for an object that is both a {@link com.bumptech.glide.request.target.Target} and a\n * {@link java.util.concurrent.Future}. For example:\n *\n * <pre>{@code\n * FutureTarget<Bitmap> futureTarget = Glide.with(fragment)\n *                                       .load(\"http://goo.gl/1asf12\")\n *                                       .asBitmap()\n *                                       .into(250, 250);\n * Bitmap myBitmap = futureTarget.get();\n * ... // do things with bitmap and then release when finished:\n * futureTarget.cancel(false);\n * }</pre>\n *\n * <p>Note - {@link #get()} and {@link #get(long, java.util.concurrent.TimeUnit)} must be called off\n * of the main thread or they will block forever.\n *\n * @param <R> The type of resource this FutureTarget will retrieve.\n */\npublic interface FutureTarget<R> extends Future<R>, Target<R> {}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/Request.java",
    "content": "package com.bumptech.glide.request;\n\n/** A request that loads a resource for an {@link com.bumptech.glide.request.target.Target}. */\npublic interface Request {\n  /** Starts an asynchronous load. */\n  void begin();\n\n  /**\n   * Prevents any bitmaps being loaded from previous requests, releases any resources held by this\n   * request, displays the current placeholder if one was provided, and marks the request as having\n   * been cancelled.\n   */\n  void clear();\n\n  /**\n   * Similar to {@link #clear} for in progress requests (or portions of a request), but does nothing\n   * if the request is already complete.\n   *\n   * <p>Unlike {@link #clear()}, this method allows implementations to act differently on subparts\n   * of a request. For example if a Request has both a thumbnail and a primary request and the\n   * thumbnail portion of the request is complete, this method allows only the primary portion of\n   * the request to be paused without clearing the previously completed thumbnail portion.\n   */\n  void pause();\n\n  /** Returns true if this request is running and has not completed or failed. */\n  boolean isRunning();\n\n  /** Returns true if the request has completed successfully. */\n  boolean isComplete();\n\n  /** Returns true if the request has been cleared. */\n  boolean isCleared();\n\n  /**\n   * Returns true if a resource is set, even if the request is not yet complete or the primary\n   * request has failed.\n   */\n  boolean isAnyResourceSet();\n\n  /**\n   * Returns {@code true} if this {@link Request} is equivalent to the given {@link Request} (has\n   * all of the same options and sizes).\n   *\n   * <p>This method is identical to {@link Object#equals(Object)} except that it's specific to\n   * {@link Request} subclasses. We do not use {@link Object#equals(Object)} directly because we\n   * track {@link Request}s in collections like {@link java.util.Set} and it's perfectly legitimate\n   * to have two different {@link Request} objects for two different {@link\n   * com.bumptech.glide.request.target.Target}s (for example). Using a similar but different method\n   * let's us selectively compare {@link Request} objects to each other when it's useful in specific\n   * scenarios.\n   */\n  boolean isEquivalentTo(Request other);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/RequestCoordinator.java",
    "content": "package com.bumptech.glide.request;\n\n/**\n * An interface for coordinating multiple requests with the same {@link\n * com.bumptech.glide.request.target.Target}.\n *\n * <p>To avoid deadlock, implemenations must <em>not</em> call into individual {@link Request}s to\n * determine their state (ie do not call {@link Request#isCleared()} or {@link Request#isRunning()}\n * etc). Instead use {@link RequestState} and the various methods available on this interface and\n * {@link Request} to track states manually.\n */\npublic interface RequestCoordinator {\n\n  /**\n   * Returns true if the {@link Request} can display a loaded bitmap.\n   *\n   * @param request The {@link Request} requesting permission to display a bitmap.\n   */\n  boolean canSetImage(Request request);\n\n  /**\n   * Returns true if the {@link Request} can display a placeholder.\n   *\n   * @param request The {@link Request} requesting permission to display a placeholder.\n   */\n  boolean canNotifyStatusChanged(Request request);\n\n  /**\n   * Returns {@code true} if the {@link Request} can clear the {@link\n   * com.bumptech.glide.request.target.Target}.\n   */\n  boolean canNotifyCleared(Request request);\n\n  /**\n   * Returns true if any coordinated {@link Request} has successfully completed.\n   *\n   * @see Request#isComplete()\n   */\n  boolean isAnyResourceSet();\n\n  /** Must be called when a {@link Request} coordinated by this object completes successfully. */\n  void onRequestSuccess(Request request);\n\n  /** Must be called when a {@link Request} coordinated by this object fails. */\n  void onRequestFailed(Request request);\n\n  /** Returns the top most parent {@code RequestCoordinator}. */\n  RequestCoordinator getRoot();\n\n  /** A simple state enum to keep track of the states of individual subrequests. */\n  enum RequestState {\n    RUNNING(false),\n    PAUSED(false),\n    CLEARED(false),\n    SUCCESS(true),\n    FAILED(true);\n\n    private final boolean isComplete;\n\n    RequestState(boolean isComplete) {\n\n      this.isComplete = isComplete;\n    }\n\n    boolean isComplete() {\n      return isComplete;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/RequestFutureTarget.java",
    "content": "package com.bumptech.glide.request;\n\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Util;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\n/**\n * A {@link java.util.concurrent.Future} implementation for Glide that can be used to load resources\n * in a blocking manner on background threads.\n *\n * <p>Note - Unlike most targets, RequestFutureTargets can be used once and only once. Attempting to\n * reuse a RequestFutureTarget will probably result in undesirable behavior or exceptions. Instead\n * of reusing objects of this class, the pattern should be:\n *\n * <pre>{@code\n * FutureTarget<File> target = null;\n * RequestManager requestManager = Glide.with(context);\n * try {\n *   target = requestManager\n *      .downloadOnly()\n *      .load(model)\n *      .submit();\n *   File downloadedFile = target.get();\n *   // ... do something with the file (usually throws IOException)\n * } catch (ExecutionException | InterruptedException | IOException e) {\n *   // ... bug reporting or recovery\n * } finally {\n *   // make sure to cancel pending operations and free resources\n *   if (target != null) {\n *     target.cancel(true); // mayInterruptIfRunning\n *   }\n * }\n * }</pre>\n *\n * The {@link #cancel(boolean)} call will cancel pending operations and make sure that any resources\n * used are recycled.\n *\n * @param <R> The type of the resource that will be loaded.\n */\npublic class RequestFutureTarget<R> implements FutureTarget<R>, RequestListener<R> {\n  private static final Waiter DEFAULT_WAITER = new Waiter();\n\n  private final int width;\n  private final int height;\n  // Exists for testing only.\n  private final boolean assertBackgroundThread;\n  private final Waiter waiter;\n\n  @GuardedBy(\"this\")\n  @Nullable\n  private R resource;\n\n  @GuardedBy(\"this\")\n  @Nullable\n  private Request request;\n\n  @GuardedBy(\"this\")\n  private boolean isCancelled;\n\n  @GuardedBy(\"this\")\n  private boolean resultReceived;\n\n  @GuardedBy(\"this\")\n  private boolean loadFailed;\n\n  @GuardedBy(\"this\")\n  @Nullable\n  private GlideException exception;\n\n  /** Constructor for a RequestFutureTarget. Should not be used directly. */\n  public RequestFutureTarget(int width, int height) {\n    this(width, height, true, DEFAULT_WAITER);\n  }\n\n  RequestFutureTarget(int width, int height, boolean assertBackgroundThread, Waiter waiter) {\n    this.width = width;\n    this.height = height;\n    this.assertBackgroundThread = assertBackgroundThread;\n    this.waiter = waiter;\n  }\n\n  @Override\n  public boolean cancel(boolean mayInterruptIfRunning) {\n    Request toClear = null;\n    synchronized (this) {\n      if (isDone()) {\n        return false;\n      }\n\n      isCancelled = true;\n      waiter.notifyAll(this);\n      if (mayInterruptIfRunning) {\n        toClear = request;\n        request = null;\n      }\n    }\n\n    // Avoid deadlock by clearing outside of the lock (b/138335419)\n    if (toClear != null) {\n      toClear.clear();\n    }\n    return true;\n  }\n\n  @Override\n  public synchronized boolean isCancelled() {\n    return isCancelled;\n  }\n\n  @Override\n  public synchronized boolean isDone() {\n    return isCancelled || resultReceived || loadFailed;\n  }\n\n  @Override\n  public R get() throws InterruptedException, ExecutionException {\n    try {\n      return doGet(null);\n    } catch (TimeoutException e) {\n      throw new AssertionError(e);\n    }\n  }\n\n  @Override\n  public R get(long time, @NonNull TimeUnit timeUnit)\n      throws InterruptedException, ExecutionException, TimeoutException {\n    return doGet(timeUnit.toMillis(time));\n  }\n\n  /** A callback that should never be invoked directly. */\n  @Override\n  public void getSize(@NonNull SizeReadyCallback cb) {\n    cb.onSizeReady(width, height);\n  }\n\n  @Override\n  public void removeCallback(@NonNull SizeReadyCallback cb) {\n    // Do nothing because we do not retain references to SizeReadyCallbacks.\n  }\n\n  @Override\n  public synchronized void setRequest(@Nullable Request request) {\n    this.request = request;\n  }\n\n  @Override\n  @Nullable\n  public synchronized Request getRequest() {\n    return request;\n  }\n\n  /** A callback that should never be invoked directly. */\n  @Override\n  public void onLoadCleared(@Nullable Drawable placeholder) {\n    // Do nothing.\n  }\n\n  /** A callback that should never be invoked directly. */\n  @Override\n  public void onLoadStarted(@Nullable Drawable placeholder) {\n    // Do nothing.\n  }\n\n  /** A callback that should never be invoked directly. */\n  @Override\n  public synchronized void onLoadFailed(@Nullable Drawable errorDrawable) {\n    // Ignored, synchronized for backwards compatibility.\n  }\n\n  /** A callback that should never be invoked directly. */\n  @Override\n  public synchronized void onResourceReady(\n      @NonNull R resource, @Nullable Transition<? super R> transition) {\n    // Ignored, synchronized for backwards compatibility.\n  }\n\n  private synchronized R doGet(Long timeoutMillis)\n      throws ExecutionException, InterruptedException, TimeoutException {\n    if (assertBackgroundThread && !isDone()) {\n      Util.assertBackgroundThread();\n    }\n\n    if (isCancelled) {\n      throw new CancellationException();\n    } else if (loadFailed) {\n      throw new ExecutionException(exception);\n    } else if (resultReceived) {\n      return resource;\n    }\n\n    if (timeoutMillis == null) {\n      waiter.waitForTimeout(this, 0);\n    } else if (timeoutMillis > 0) {\n      long now = System.currentTimeMillis();\n      long deadline = now + timeoutMillis;\n      while (!isDone() && now < deadline) {\n        waiter.waitForTimeout(this, deadline - now);\n        now = System.currentTimeMillis();\n      }\n    }\n\n    if (Thread.interrupted()) {\n      throw new InterruptedException();\n    } else if (loadFailed) {\n      throw new ExecutionException(exception);\n    } else if (isCancelled) {\n      throw new CancellationException();\n    } else if (!resultReceived) {\n      throw new TimeoutException();\n    }\n\n    return resource;\n  }\n\n  @Override\n  public void onStart() {\n    // Do nothing.\n  }\n\n  @Override\n  public void onStop() {\n    // Do nothing.\n  }\n\n  @Override\n  public void onDestroy() {\n    // Do nothing.\n  }\n\n  @Override\n  public synchronized boolean onLoadFailed(\n      @Nullable GlideException e, Object model, Target<R> target, boolean isFirstResource) {\n    loadFailed = true;\n    exception = e;\n    waiter.notifyAll(this);\n    return false;\n  }\n\n  @Override\n  public synchronized boolean onResourceReady(\n      R resource, Object model, Target<R> target, DataSource dataSource, boolean isFirstResource) {\n    // We might get a null result.\n    resultReceived = true;\n    this.resource = resource;\n    waiter.notifyAll(this);\n    return false;\n  }\n\n  @Override\n  public String toString() {\n    String toString = super.toString() + \"[status=\";\n    final String status;\n    Request pendingRequest = null;\n    synchronized (this) {\n      if (isCancelled) {\n        status = \"CANCELLED\";\n      } else if (loadFailed) {\n        status = \"FAILURE\";\n      } else if (resultReceived) {\n        status = \"SUCCESS\";\n      } else {\n        status = \"PENDING\";\n        pendingRequest = request;\n      }\n    }\n    if (pendingRequest != null) {\n      return toString + status + \", request=[\" + pendingRequest + \"]]\";\n    }\n    return toString + status + \"]\";\n  }\n\n  @VisibleForTesting\n  static class Waiter {\n    // This is a simple wrapper class that is used to enable testing. The call to the wrapping class\n    // is waited on appropriately.\n    @SuppressWarnings(\"WaitNotInLoop\")\n    void waitForTimeout(Object toWaitOn, long timeoutMillis) throws InterruptedException {\n      toWaitOn.wait(timeoutMillis);\n    }\n\n    void notifyAll(Object toNotify) {\n      toNotify.notifyAll();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/RequestListener.java",
    "content": "package com.bumptech.glide.request;\n\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\n\n/**\n * A class for monitoring the status of a request while images load.\n *\n * <p>All methods in this interface will be called from a background thread if the {@code\n * RequestListener} is added to a request that is started with {@link RequestBuilder#submit()},\n * {@link RequestBuilder#submit(int, int)}, or {@link RequestBuilder#into(int, int)}. Those methods\n * no longer post results back to the main thread to avoid the unnecessary thread interactions and\n * corresponding latency. As a side affect though, listeners added to those requests are no longer\n * called on the main thread. {@code RequestListeners} added to requests started with {@link\n * RequestBuilder#into(Target)} or {@link RequestBuilder#into(ImageView)} will continue to be called\n * back on the main thread.\n *\n * @param <R> The type of resource being loaded.\n */\npublic interface RequestListener<R> {\n\n  /**\n   * Called when an exception occurs during a load, immediately before {@link\n   * Target#onLoadFailed(Drawable)}. Will only be called if we currently want to display an image\n   * for the given model in the given target. It is recommended to create a single instance per\n   * activity/fragment rather than instantiate a new object for each call to {@code\n   * Glide.with(fragment/activity).load()} to avoid object churn.\n   *\n   * <p>It is not safe to reload this or a different model in this callback. If you need to do so\n   * use {@link com.bumptech.glide.RequestBuilder#error(RequestBuilder)} instead.\n   *\n   * <p>Although you can't start an entirely new load, it is safe to change what is displayed in the\n   * {@link Target} at this point, as long as you return {@code true} from the method to prevent\n   * {@link Target#onLoadFailed(Drawable)} from being called.\n   *\n   * <p>For threading guarantees, see the class comment.\n   *\n   * <p>For example:\n   *\n   * <pre>{@code\n   * public boolean onLoadFailed(Exception e, T model, Target target, boolean isFirstResource) {\n   *     target.setPlaceholder(R.drawable.a_specific_error_for_my_exception);\n   *     return true; // Prevent onLoadFailed from being called on the Target.\n   * }\n   * }</pre>\n   *\n   * @param e The maybe {@code null} exception containing information about why the request failed.\n   * @param model The model we were trying to load when the exception occurred.\n   * @param target The {@link Target} we were trying to load the image into.\n   * @param isFirstResource {@code true} if this exception is for the first resource to load.\n   * @return {@code true} to prevent {@link Target#onLoadFailed(Drawable)} from being called on\n   *     {@code target}, typically because the listener wants to update the {@code target} or the\n   *     object the {@code target} wraps itself or {@code false} to allow {@link\n   *     Target#onLoadFailed(Drawable)} to be called on {@code target}.\n   */\n  boolean onLoadFailed(\n      @Nullable GlideException e, Object model, Target<R> target, boolean isFirstResource);\n\n  /**\n   * Called when a load completes successfully, immediately before {@link\n   * Target#onResourceReady(Object, com.bumptech.glide.request.transition.Transition)}.\n   *\n   * <p>For threading guarantees, see the class comment.\n   *\n   * @param resource The resource that was loaded for the target. Non-null because a null resource\n   *     will result in a call to {@link #onLoadFailed(GlideException, Object, Target, boolean)}\n   *     instead of this method.\n   * @param model The specific model that was used to load the image. Non-null because a null model\n   *     will result in a call to {@link #onLoadFailed(GlideException, Object, Target, boolean)}\n   *     instead of this method.\n   * @param target The target the model was loaded into.\n   * @param dataSource The {@link DataSource} the resource was loaded from.\n   * @param isFirstResource {@code true} if this is the first resource to in this load to be loaded\n   *     into the target. For example when loading a thumbnail and a full-sized image, this will be\n   *     {@code true} for the first image to load and {@code false} for the second.\n   * @return {@code true} to prevent {@link Target#onResourceReady(Object, Transition)} from being\n   *     called on {@code target}, typically because the listener wants to update the {@code target}\n   *     or the object the {@code target} wraps itself or {@code false} to allow {@link\n   *     Target#onResourceReady(Object, Transition)} to be called on {@code target}.\n   */\n  boolean onResourceReady(\n      R resource, Object model, Target<R> target, DataSource dataSource, boolean isFirstResource);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/RequestOptions.java",
    "content": "package com.bumptech.glide.request;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.IntRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\n\n/**\n * Provides type independent options to customize loads with Glide.\n *\n * <p>Non-final to allow Glide's generated classes to be assignable to their non-generated\n * equivalents.\n */\n@SuppressWarnings(\"PMD.UseUtilityClass\")\npublic class RequestOptions extends BaseRequestOptions<RequestOptions> {\n\n  @Nullable private static RequestOptions skipMemoryCacheTrueOptions;\n  @Nullable private static RequestOptions skipMemoryCacheFalseOptions;\n  @Nullable private static RequestOptions fitCenterOptions;\n  @Nullable private static RequestOptions centerInsideOptions;\n  @Nullable private static RequestOptions centerCropOptions;\n  @Nullable private static RequestOptions circleCropOptions;\n  @Nullable private static RequestOptions noTransformOptions;\n  @Nullable private static RequestOptions noAnimationOptions;\n\n  /** Returns a {@link RequestOptions} object with {@link #sizeMultiplier(float)} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions sizeMultiplierOf(\n      @FloatRange(from = 0, to = 1) float sizeMultiplier) {\n    return new RequestOptions().sizeMultiplier(sizeMultiplier);\n  }\n\n  /**\n   * Returns a {@link RequestOptions} object with {@link #diskCacheStrategy(DiskCacheStrategy)} set.\n   */\n  @NonNull\n  @CheckResult\n  public static RequestOptions diskCacheStrategyOf(@NonNull DiskCacheStrategy diskCacheStrategy) {\n    return new RequestOptions().diskCacheStrategy(diskCacheStrategy);\n  }\n\n  /**\n   * Returns a {@link RequestOptions} object with {@link BaseRequestOptions#priority(Priority)}}\n   * set.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions priorityOf(@NonNull Priority priority) {\n    return new RequestOptions().priority(priority);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #placeholder(Drawable)} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions placeholderOf(@Nullable Drawable placeholder) {\n    return new RequestOptions().placeholder(placeholder);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #placeholder(int)} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions placeholderOf(@DrawableRes int placeholderId) {\n    return new RequestOptions().placeholder(placeholderId);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #error(Drawable)} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions errorOf(@Nullable Drawable errorDrawable) {\n    return new RequestOptions().error(errorDrawable);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #error(int)}} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions errorOf(@DrawableRes int errorId) {\n    return new RequestOptions().error(errorId);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #skipMemoryCache(boolean)} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions skipMemoryCacheOf(boolean skipMemoryCache) {\n    if (skipMemoryCache) {\n      if (skipMemoryCacheTrueOptions == null) {\n        skipMemoryCacheTrueOptions = new RequestOptions().skipMemoryCache(true).autoClone();\n      }\n      return skipMemoryCacheTrueOptions;\n    } else {\n      if (skipMemoryCacheFalseOptions == null) {\n        skipMemoryCacheFalseOptions = new RequestOptions().skipMemoryCache(false).autoClone();\n      }\n      return skipMemoryCacheFalseOptions;\n    }\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #override(int, int)}} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions overrideOf(int width, int height) {\n    return new RequestOptions().override(width, height);\n  }\n\n  /**\n   * Returns a {@link RequestOptions} with {@link #override(int, int)} set where both the width and\n   * height are the given size.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions overrideOf(int size) {\n    return overrideOf(size, size);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #signature} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions signatureOf(@NonNull Key signature) {\n    return new RequestOptions().signature(signature);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #fitCenter()} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions fitCenterTransform() {\n    if (fitCenterOptions == null) {\n      fitCenterOptions = new RequestOptions().fitCenter().autoClone();\n    }\n    return fitCenterOptions;\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #centerInside()} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions centerInsideTransform() {\n    if (centerInsideOptions == null) {\n      centerInsideOptions = new RequestOptions().centerInside().autoClone();\n    }\n    return centerInsideOptions;\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #centerCrop()} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions centerCropTransform() {\n    if (centerCropOptions == null) {\n      centerCropOptions = new RequestOptions().centerCrop().autoClone();\n    }\n    return centerCropOptions;\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link RequestOptions#circleCrop()} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions circleCropTransform() {\n    if (circleCropOptions == null) {\n      circleCropOptions = new RequestOptions().circleCrop().autoClone();\n    }\n    return circleCropOptions;\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #transform(Transformation)} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions bitmapTransform(@NonNull Transformation<Bitmap> transformation) {\n    return new RequestOptions().transform(transformation);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #dontTransform()} set. */\n  @SuppressWarnings(\"WeakerAccess\")\n  @NonNull\n  @CheckResult\n  public static RequestOptions noTransformation() {\n    if (noTransformOptions == null) {\n      noTransformOptions = new RequestOptions().dontTransform().autoClone();\n    }\n    return noTransformOptions;\n  }\n\n  /**\n   * Returns a {@link RequestOptions} object with the given {@link Option} set via {@link\n   * #set(Option, Object)}.\n   */\n  @NonNull\n  @CheckResult\n  public static <T> RequestOptions option(@NonNull Option<T> option, @NonNull T value) {\n    return new RequestOptions().set(option, value);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #decode(Class)} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions decodeTypeOf(@NonNull Class<?> resourceClass) {\n    return new RequestOptions().decode(resourceClass);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #format(DecodeFormat)} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions formatOf(@NonNull DecodeFormat format) {\n    return new RequestOptions().format(format);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #frame(long)} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions frameOf(@IntRange(from = 0) long frameTimeMicros) {\n    return new RequestOptions().frame(frameTimeMicros);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #downsample(DownsampleStrategy)} set. */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions downsampleOf(@NonNull DownsampleStrategy strategy) {\n    return new RequestOptions().downsample(strategy);\n  }\n\n  /** Returns a {@link RequestOptions} object with {@link #timeout(int)} set. */\n  @NonNull\n  @CheckResult\n  public static RequestOptions timeoutOf(@IntRange(from = 0) int timeout) {\n    return new RequestOptions().timeout(timeout);\n  }\n\n  /**\n   * Returns a {@link com.bumptech.glide.request.RequestOptions} with {@link #encodeQuality(int)}\n   * called with the given quality.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions encodeQualityOf(@IntRange(from = 0, to = 100) int quality) {\n    return new RequestOptions().encodeQuality(quality);\n  }\n\n  /**\n   * Returns a {@link com.bumptech.glide.request.RequestOptions} with {@link\n   * #encodeFormat(android.graphics.Bitmap.CompressFormat)} called with the given format.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions encodeFormatOf(@NonNull Bitmap.CompressFormat format) {\n    return new RequestOptions().encodeFormat(format);\n  }\n\n  /**\n   * Returns a new {@link com.bumptech.glide.request.RequestOptions} with {@link #dontAnimate()}\n   * called.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  @CheckResult\n  public static RequestOptions noAnimation() {\n    if (noAnimationOptions == null) {\n      noAnimationOptions = new RequestOptions().dontAnimate().autoClone();\n    }\n    return noAnimationOptions;\n  }\n\n  // Make sure that we're not equal to any other concrete implementation of RequestOptions.\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof RequestOptions && super.equals(o);\n  }\n\n  // Our class doesn't include any additional properties, so we don't need to modify hashcode, but\n  // keep it here as a reminder in case we add properties.\n  @SuppressWarnings(\"PMD.UselessOverridingMethod\")\n  @Override\n  public int hashCode() {\n    return super.hashCode();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/ResourceCallback.java",
    "content": "package com.bumptech.glide.request;\n\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.engine.Resource;\n\n/**\n * A callback that listens for when a resource load completes successfully or fails due to an\n * exception.\n */\npublic interface ResourceCallback {\n\n  /**\n   * Called when a resource is successfully loaded.\n   *\n   * @param resource The loaded resource.\n   */\n  void onResourceReady(\n      Resource<?> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey);\n\n  /**\n   * Called when a resource fails to load successfully.\n   *\n   * @param e a non-null {@link GlideException}.\n   */\n  void onLoadFailed(GlideException e);\n\n  /** Returns the lock to use when notifying individual requests. */\n  Object getLock();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/SingleRequest.java",
    "content": "package com.bumptech.glide.request;\n\nimport android.content.Context;\nimport android.content.res.Resources.Theme;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.GlideBuilder.LogRequestOrigins;\nimport com.bumptech.glide.GlideContext;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.Engine;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.resource.drawable.DrawableDecoderCompat;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.request.transition.TransitionFactory;\nimport com.bumptech.glide.util.LogTime;\nimport com.bumptech.glide.util.Util;\nimport com.bumptech.glide.util.pool.GlideTrace;\nimport com.bumptech.glide.util.pool.StateVerifier;\nimport java.util.List;\nimport java.util.concurrent.Executor;\n\n/**\n * A {@link Request} that loads a {@link com.bumptech.glide.load.engine.Resource} into a given\n * {@link Target}.\n *\n * @param <R> The type of the resource that will be transcoded from the loaded resource.\n */\npublic final class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {\n  /** Tag for logging internal events, not generally suitable for public use. */\n  private static final String TAG = \"GlideRequest\";\n\n  /** Tag for logging externally useful events (request completion, timing etc). */\n  private static final String GLIDE_TAG = \"Glide\";\n\n  private static final boolean IS_VERBOSE_LOGGABLE = Log.isLoggable(TAG, Log.VERBOSE);\n  private int cookie;\n\n  private enum Status {\n    /** Created but not yet running. */\n    PENDING,\n    /** In the process of fetching media. */\n    RUNNING,\n    /** Waiting for a callback given to the Target to be called to determine target dimensions. */\n    WAITING_FOR_SIZE,\n    /** Finished loading media successfully. */\n    COMPLETE,\n    /** Failed to load media, may be restarted. */\n    FAILED,\n    /** Cleared by the user with a placeholder set, may be restarted. */\n    CLEARED,\n  }\n\n  @Nullable\n  private final String tag = IS_VERBOSE_LOGGABLE ? String.valueOf(super.hashCode()) : null;\n\n  private final StateVerifier stateVerifier = StateVerifier.newInstance();\n\n  /* Variables mutated only when a request is initialized or returned to the object pool. */\n  private final Object requestLock;\n\n  @Nullable private final RequestListener<R> targetListener;\n\n  private final RequestCoordinator requestCoordinator;\n\n  private final Context context;\n\n  private final GlideContext glideContext;\n\n  @Nullable private final Object model;\n\n  private final Class<R> transcodeClass;\n\n  private final BaseRequestOptions<?> requestOptions;\n\n  private final int overrideWidth;\n\n  private final int overrideHeight;\n\n  private final Priority priority;\n\n  private final Target<R> target;\n\n  @Nullable private final List<RequestListener<R>> requestListeners;\n\n  private final TransitionFactory<? super R> animationFactory;\n\n  private final Executor callbackExecutor;\n\n  @GuardedBy(\"requestLock\")\n  private Resource<R> resource;\n\n  @GuardedBy(\"requestLock\")\n  private Engine.LoadStatus loadStatus;\n\n  @GuardedBy(\"requestLock\")\n  private long startTime;\n\n  // Volatile because it's accessed outside of a lock and nullable, even though in practice it will\n  // always be non-null unless the request is in the object pool.\n  private volatile Engine engine;\n\n  /* Variables mutated during a request. */\n  @GuardedBy(\"requestLock\")\n  private Status status;\n\n  @GuardedBy(\"requestLock\")\n  @Nullable\n  private Drawable errorDrawable;\n\n  @GuardedBy(\"requestLock\")\n  @Nullable\n  private Drawable placeholderDrawable;\n\n  @GuardedBy(\"requestLock\")\n  @Nullable\n  private Drawable fallbackDrawable;\n\n  @GuardedBy(\"requestLock\")\n  private int width;\n\n  @GuardedBy(\"requestLock\")\n  private int height;\n\n  @GuardedBy(\"requestLock\")\n  private boolean isCallingCallbacks;\n\n  @Nullable private RuntimeException requestOrigin;\n\n  public static <R> SingleRequest<R> obtain(\n      Context context,\n      GlideContext glideContext,\n      Object requestLock,\n      Object model,\n      Class<R> transcodeClass,\n      BaseRequestOptions<?> requestOptions,\n      int overrideWidth,\n      int overrideHeight,\n      Priority priority,\n      Target<R> target,\n      RequestListener<R> targetListener,\n      @Nullable List<RequestListener<R>> requestListeners,\n      RequestCoordinator requestCoordinator,\n      Engine engine,\n      TransitionFactory<? super R> animationFactory,\n      Executor callbackExecutor) {\n    return new SingleRequest<>(\n        context,\n        glideContext,\n        requestLock,\n        model,\n        transcodeClass,\n        requestOptions,\n        overrideWidth,\n        overrideHeight,\n        priority,\n        target,\n        targetListener,\n        requestListeners,\n        requestCoordinator,\n        engine,\n        animationFactory,\n        callbackExecutor);\n  }\n\n  // We are in fact locking on the same lock that will be used for all subsequent method calls.\n  @SuppressWarnings(\"GuardedBy\")\n  private SingleRequest(\n      Context context,\n      GlideContext glideContext,\n      @NonNull Object requestLock,\n      @Nullable Object model,\n      Class<R> transcodeClass,\n      BaseRequestOptions<?> requestOptions,\n      int overrideWidth,\n      int overrideHeight,\n      Priority priority,\n      Target<R> target,\n      @Nullable RequestListener<R> targetListener,\n      @Nullable List<RequestListener<R>> requestListeners,\n      RequestCoordinator requestCoordinator,\n      Engine engine,\n      TransitionFactory<? super R> animationFactory,\n      Executor callbackExecutor) {\n    this.requestLock = requestLock;\n    this.context = context;\n    this.glideContext = glideContext;\n    this.model = model;\n    this.transcodeClass = transcodeClass;\n    this.requestOptions = requestOptions;\n    this.overrideWidth = overrideWidth;\n    this.overrideHeight = overrideHeight;\n    this.priority = priority;\n    this.target = target;\n    this.targetListener = targetListener;\n    this.requestListeners = requestListeners;\n    this.requestCoordinator = requestCoordinator;\n    this.engine = engine;\n    this.animationFactory = animationFactory;\n    this.callbackExecutor = callbackExecutor;\n    status = Status.PENDING;\n\n    if (requestOrigin == null && glideContext.getExperiments().isEnabled(LogRequestOrigins.class)) {\n      requestOrigin = new RuntimeException(\"Glide request origin trace\");\n    }\n  }\n\n  @Override\n  public void begin() {\n    synchronized (requestLock) {\n      assertNotCallingCallbacks();\n      stateVerifier.throwIfRecycled();\n      startTime = LogTime.getLogTime();\n      if (model == null) {\n        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {\n          width = overrideWidth;\n          height = overrideHeight;\n        }\n        // Only log at more verbose log levels if the user has set a fallback drawable, because\n        // fallback Drawables indicate the user expects null models occasionally.\n        int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;\n        onLoadFailed(new GlideException(\"Received null model\"), logLevel);\n        return;\n      }\n\n      if (status == Status.RUNNING) {\n        throw new IllegalArgumentException(\"Cannot restart a running request\");\n      }\n\n      // If we're restarted after we're complete (usually via something like a notifyDataSetChanged\n      // that starts an identical request into the same Target or View), we can simply use the\n      // resource and size we retrieved the last time around and skip obtaining a new size, starting\n      // a new load etc. This does mean that users who want to restart a load because they expect\n      // that the view size has changed will need to explicitly clear the View or Target before\n      // starting the new load.\n      if (status == Status.COMPLETE) {\n        onResourceReady(\n            resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n        return;\n      }\n\n      // Restarts for requests that are neither complete nor running can be treated as new requests\n      // and can run again from the beginning.\n\n      experimentalNotifyRequestStarted(model);\n\n      cookie = GlideTrace.beginSectionAsync(TAG);\n      status = Status.WAITING_FOR_SIZE;\n      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {\n        onSizeReady(overrideWidth, overrideHeight);\n      } else {\n        target.getSize(this);\n      }\n\n      if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)\n          && canNotifyStatusChanged()) {\n        target.onLoadStarted(getPlaceholderDrawable());\n      }\n      if (IS_VERBOSE_LOGGABLE) {\n        logV(\"finished run method in \" + LogTime.getElapsedMillis(startTime));\n      }\n    }\n  }\n\n  private void experimentalNotifyRequestStarted(Object model) {\n    if (requestListeners == null) {\n      return;\n    }\n    for (RequestListener<?> requestListener : requestListeners) {\n      if (requestListener instanceof ExperimentalRequestListener) {\n        ((ExperimentalRequestListener<?>) requestListener).onRequestStarted(model);\n      }\n    }\n  }\n\n  /**\n   * Cancels the current load but does not release any resources held by the request and continues\n   * to display the loaded resource if the load completed before the call to cancel.\n   *\n   * <p>Cancelled requests can be restarted with a subsequent call to {@link #begin()}.\n   *\n   * @see #clear()\n   */\n  @GuardedBy(\"requestLock\")\n  private void cancel() {\n    assertNotCallingCallbacks();\n    stateVerifier.throwIfRecycled();\n    target.removeCallback(this);\n    if (loadStatus != null) {\n      loadStatus.cancel();\n      loadStatus = null;\n    }\n  }\n\n  // Avoids difficult to understand errors like #2413.\n  @GuardedBy(\"requestLock\")\n  private void assertNotCallingCallbacks() {\n    if (isCallingCallbacks) {\n      throw new IllegalStateException(\n          \"You can't start or clear loads in RequestListener or\"\n              + \" Target callbacks. If you're trying to start a fallback request when a load fails,\"\n              + \" use RequestBuilder#error(RequestBuilder). Otherwise consider posting your into()\"\n              + \" or clear() calls to the main thread using a Handler instead.\");\n    }\n  }\n\n  /**\n   * Cancels the current load if it is in progress, clears any resources held onto by the request\n   * and replaces the loaded resource if the load completed with the placeholder.\n   *\n   * <p>Cleared requests can be restarted with a subsequent call to {@link #begin()}\n   *\n   * @see #cancel()\n   */\n  @Override\n  public void clear() {\n    Resource<R> toRelease = null;\n    synchronized (requestLock) {\n      assertNotCallingCallbacks();\n      stateVerifier.throwIfRecycled();\n      if (status == Status.CLEARED) {\n        return;\n      }\n      cancel();\n      // Resource must be released before canNotifyStatusChanged is called.\n      if (resource != null) {\n        toRelease = resource;\n        resource = null;\n      }\n      if (canNotifyCleared()) {\n        target.onLoadCleared(getPlaceholderDrawable());\n      }\n\n      GlideTrace.endSectionAsync(TAG, cookie);\n      status = Status.CLEARED;\n    }\n\n    if (toRelease != null) {\n      engine.release(toRelease);\n    }\n  }\n\n  @Override\n  public void pause() {\n    synchronized (requestLock) {\n      if (isRunning()) {\n        clear();\n      }\n    }\n  }\n\n  @Override\n  public boolean isRunning() {\n    synchronized (requestLock) {\n      return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE;\n    }\n  }\n\n  @Override\n  public boolean isComplete() {\n    synchronized (requestLock) {\n      return status == Status.COMPLETE;\n    }\n  }\n\n  @Override\n  public boolean isCleared() {\n    synchronized (requestLock) {\n      return status == Status.CLEARED;\n    }\n  }\n\n  @Override\n  public boolean isAnyResourceSet() {\n    synchronized (requestLock) {\n      return status == Status.COMPLETE;\n    }\n  }\n\n  @GuardedBy(\"requestLock\")\n  private Drawable getErrorDrawable() {\n    if (errorDrawable == null) {\n      errorDrawable = requestOptions.getErrorPlaceholder();\n      if (errorDrawable == null && requestOptions.getErrorId() > 0) {\n        errorDrawable = loadDrawable(requestOptions.getErrorId());\n      }\n    }\n    return errorDrawable;\n  }\n\n  @GuardedBy(\"requestLock\")\n  private Drawable getPlaceholderDrawable() {\n    if (placeholderDrawable == null) {\n      placeholderDrawable = requestOptions.getPlaceholderDrawable();\n      if (placeholderDrawable == null && requestOptions.getPlaceholderId() > 0) {\n        placeholderDrawable = loadDrawable(requestOptions.getPlaceholderId());\n      }\n    }\n    return placeholderDrawable;\n  }\n\n  @GuardedBy(\"requestLock\")\n  private Drawable getFallbackDrawable() {\n    if (fallbackDrawable == null) {\n      fallbackDrawable = requestOptions.getFallbackDrawable();\n      if (fallbackDrawable == null && requestOptions.getFallbackId() > 0) {\n        fallbackDrawable = loadDrawable(requestOptions.getFallbackId());\n      }\n    }\n    return fallbackDrawable;\n  }\n\n  @GuardedBy(\"requestLock\")\n  private Drawable loadDrawable(@DrawableRes int resourceId) {\n    Theme theme =\n        requestOptions.getTheme() != null ? requestOptions.getTheme() : context.getTheme();\n    return DrawableDecoderCompat.getDrawable(context, resourceId, theme);\n  }\n\n  @GuardedBy(\"requestLock\")\n  private void setErrorPlaceholder() {\n    if (!canNotifyStatusChanged()) {\n      return;\n    }\n\n    Drawable error = null;\n    if (model == null) {\n      error = getFallbackDrawable();\n    }\n    // Either the model isn't null, or there was no fallback drawable set.\n    if (error == null) {\n      error = getErrorDrawable();\n    }\n    // The model isn't null, no fallback drawable was set or no error drawable was set.\n    if (error == null) {\n      error = getPlaceholderDrawable();\n    }\n    target.onLoadFailed(error);\n  }\n\n  /** A callback method that should never be invoked directly. */\n  @Override\n  public void onSizeReady(int width, int height) {\n    stateVerifier.throwIfRecycled();\n    synchronized (requestLock) {\n      if (IS_VERBOSE_LOGGABLE) {\n        logV(\"Got onSizeReady in \" + LogTime.getElapsedMillis(startTime));\n      }\n      if (status != Status.WAITING_FOR_SIZE) {\n        return;\n      }\n      status = Status.RUNNING;\n\n      float sizeMultiplier = requestOptions.getSizeMultiplier();\n      this.width = maybeApplySizeMultiplier(width, sizeMultiplier);\n      this.height = maybeApplySizeMultiplier(height, sizeMultiplier);\n\n      if (IS_VERBOSE_LOGGABLE) {\n        logV(\"finished setup for calling load in \" + LogTime.getElapsedMillis(startTime));\n      }\n      loadStatus =\n          engine.load(\n              glideContext,\n              model,\n              requestOptions.getSignature(),\n              this.width,\n              this.height,\n              requestOptions.getResourceClass(),\n              transcodeClass,\n              priority,\n              requestOptions.getDiskCacheStrategy(),\n              requestOptions.getTransformations(),\n              requestOptions.isTransformationRequired(),\n              requestOptions.isScaleOnlyOrNoTransform(),\n              requestOptions.getOptions(),\n              requestOptions.isMemoryCacheable(),\n              requestOptions.getUseUnlimitedSourceGeneratorsPool(),\n              requestOptions.getUseAnimationPool(),\n              requestOptions.getOnlyRetrieveFromCache(),\n              this,\n              callbackExecutor);\n\n      // This is a hack that's only useful for testing right now where loads complete synchronously\n      // even though under any executor running on any thread but the main thread, the load would\n      // have completed asynchronously.\n      if (status != Status.RUNNING) {\n        loadStatus = null;\n      }\n      if (IS_VERBOSE_LOGGABLE) {\n        logV(\"finished onSizeReady in \" + LogTime.getElapsedMillis(startTime));\n      }\n    }\n  }\n\n  private static int maybeApplySizeMultiplier(int size, float sizeMultiplier) {\n    return size == Target.SIZE_ORIGINAL ? size : Math.round(sizeMultiplier * size);\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean canSetResource() {\n    return requestCoordinator == null || requestCoordinator.canSetImage(this);\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean canNotifyCleared() {\n    return requestCoordinator == null || requestCoordinator.canNotifyCleared(this);\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean canNotifyStatusChanged() {\n    return requestCoordinator == null || requestCoordinator.canNotifyStatusChanged(this);\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean isFirstReadyResource() {\n    return requestCoordinator == null || !requestCoordinator.getRoot().isAnyResourceSet();\n  }\n\n  @GuardedBy(\"requestLock\")\n  private void notifyRequestCoordinatorLoadSucceeded() {\n    if (requestCoordinator != null) {\n      requestCoordinator.onRequestSuccess(this);\n    }\n  }\n\n  @GuardedBy(\"requestLock\")\n  private void notifyRequestCoordinatorLoadFailed() {\n    if (requestCoordinator != null) {\n      requestCoordinator.onRequestFailed(this);\n    }\n  }\n\n  /** A callback method that should never be invoked directly. */\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public void onResourceReady(\n      Resource<?> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {\n    stateVerifier.throwIfRecycled();\n    Resource<?> toRelease = null;\n    try {\n      synchronized (requestLock) {\n        loadStatus = null;\n        if (resource == null) {\n          GlideException exception =\n              new GlideException(\n                  \"Expected to receive a Resource<R> with an \"\n                      + \"object of \"\n                      + transcodeClass\n                      + \" inside, but instead got null.\");\n          onLoadFailed(exception);\n          return;\n        }\n\n        Object received = resource.get();\n        if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {\n          toRelease = resource;\n          this.resource = null;\n          GlideException exception =\n              new GlideException(\n                  \"Expected to receive an object of \"\n                      + transcodeClass\n                      + \" but instead\"\n                      + \" got \"\n                      + (received != null ? received.getClass() : \"\")\n                      + \"{\"\n                      + received\n                      + \"} inside\"\n                      + \" \"\n                      + \"Resource{\"\n                      + resource\n                      + \"}.\"\n                      + (received != null\n                          ? \"\"\n                          : \" \"\n                              + \"To indicate failure return a null Resource \"\n                              + \"object, rather than a Resource object containing null data.\"));\n          onLoadFailed(exception);\n          return;\n        }\n\n        if (!canSetResource()) {\n          toRelease = resource;\n          this.resource = null;\n          // We can't put the status to complete before asking canSetResource().\n          status = Status.COMPLETE;\n          GlideTrace.endSectionAsync(TAG, cookie);\n          return;\n        }\n\n        onResourceReady(\n            (Resource<R>) resource, (R) received, dataSource, isLoadedFromAlternateCacheKey);\n      }\n    } finally {\n      if (toRelease != null) {\n        engine.release(toRelease);\n      }\n    }\n  }\n\n  /**\n   * Internal {@link #onResourceReady(Resource, DataSource, boolean)} where arguments are known to\n   * be safe.\n   *\n   * @param resource original {@link Resource}, never <code>null</code>\n   * @param result object returned by {@link Resource#get()}, checked for type and never <code>null\n   *     </code>\n   */\n  // We're using experimental APIs...\n  @SuppressWarnings({\"deprecation\", \"PMD.UnusedFormalParameter\"})\n  @GuardedBy(\"requestLock\")\n  private void onResourceReady(\n      Resource<R> resource, R result, DataSource dataSource, boolean isAlternateCacheKey) {\n    // We must call isFirstReadyResource before setting status.\n    boolean isFirstResource = isFirstReadyResource();\n    status = Status.COMPLETE;\n    this.resource = resource;\n\n    if (glideContext.getLogLevel() <= Log.DEBUG) {\n      Log.d(\n          GLIDE_TAG,\n          \"Finished loading \"\n              + result.getClass().getSimpleName()\n              + \" from \"\n              + dataSource\n              + \" for \"\n              + model\n              + \" with size [\"\n              + width\n              + \"x\"\n              + height\n              + \"] in \"\n              + LogTime.getElapsedMillis(startTime)\n              + \" ms\");\n    }\n\n    notifyRequestCoordinatorLoadSucceeded();\n\n    isCallingCallbacks = true;\n    try {\n      boolean anyListenerHandledUpdatingTarget = false;\n      if (requestListeners != null) {\n        for (RequestListener<R> listener : requestListeners) {\n          anyListenerHandledUpdatingTarget |=\n              listener.onResourceReady(result, model, target, dataSource, isFirstResource);\n\n          if (listener instanceof ExperimentalRequestListener) {\n            ExperimentalRequestListener<R> experimentalRequestListener =\n                (ExperimentalRequestListener<R>) listener;\n            anyListenerHandledUpdatingTarget |=\n                experimentalRequestListener.onResourceReady(\n                    result, model, target, dataSource, isFirstResource, isAlternateCacheKey);\n          }\n        }\n      }\n      anyListenerHandledUpdatingTarget |=\n          targetListener != null\n              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);\n\n      if (!anyListenerHandledUpdatingTarget) {\n        Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource);\n        target.onResourceReady(result, animation);\n      }\n    } finally {\n      isCallingCallbacks = false;\n    }\n\n    GlideTrace.endSectionAsync(TAG, cookie);\n  }\n\n  /** A callback method that should never be invoked directly. */\n  @Override\n  public void onLoadFailed(GlideException e) {\n    onLoadFailed(e, Log.WARN);\n  }\n\n  @Override\n  public Object getLock() {\n    stateVerifier.throwIfRecycled();\n    return requestLock;\n  }\n\n  private void onLoadFailed(GlideException e, int maxLogLevel) {\n    stateVerifier.throwIfRecycled();\n    synchronized (requestLock) {\n      e.setOrigin(requestOrigin);\n      int logLevel = glideContext.getLogLevel();\n      if (logLevel <= maxLogLevel) {\n        Log.w(\n            GLIDE_TAG,\n            \"Load failed for [\" + model + \"] with dimensions [\" + width + \"x\" + height + \"]\",\n            e);\n        if (logLevel <= Log.INFO) {\n          e.logRootCauses(GLIDE_TAG);\n        }\n      }\n\n      loadStatus = null;\n      status = Status.FAILED;\n\n      notifyRequestCoordinatorLoadFailed();\n\n      isCallingCallbacks = true;\n      try {\n        // TODO: what if this is a thumbnail request?\n        boolean anyListenerHandledUpdatingTarget = false;\n        if (requestListeners != null) {\n          for (RequestListener<R> listener : requestListeners) {\n            anyListenerHandledUpdatingTarget |=\n                listener.onLoadFailed(e, model, target, isFirstReadyResource());\n          }\n        }\n        anyListenerHandledUpdatingTarget |=\n            targetListener != null\n                && targetListener.onLoadFailed(e, model, target, isFirstReadyResource());\n\n        if (!anyListenerHandledUpdatingTarget) {\n          setErrorPlaceholder();\n        }\n      } finally {\n        isCallingCallbacks = false;\n      }\n\n      GlideTrace.endSectionAsync(TAG, cookie);\n    }\n  }\n\n  @Override\n  public boolean isEquivalentTo(Request o) {\n    if (!(o instanceof SingleRequest)) {\n      return false;\n    }\n\n    int localOverrideWidth;\n    int localOverrideHeight;\n    Object localModel;\n    Class<?> localTranscodeClass;\n    BaseRequestOptions<?> localRequestOptions;\n    Priority localPriority;\n    int localListenerCount;\n    synchronized (requestLock) {\n      localOverrideWidth = overrideWidth;\n      localOverrideHeight = overrideHeight;\n      localModel = model;\n      localTranscodeClass = transcodeClass;\n      localRequestOptions = requestOptions;\n      localPriority = priority;\n      localListenerCount = requestListeners != null ? requestListeners.size() : 0;\n    }\n\n    SingleRequest<?> other = (SingleRequest<?>) o;\n    int otherLocalOverrideWidth;\n    int otherLocalOverrideHeight;\n    Object otherLocalModel;\n    Class<?> otherLocalTranscodeClass;\n    BaseRequestOptions<?> otherLocalRequestOptions;\n    Priority otherLocalPriority;\n    int otherLocalListenerCount;\n    synchronized (other.requestLock) {\n      otherLocalOverrideWidth = other.overrideWidth;\n      otherLocalOverrideHeight = other.overrideHeight;\n      otherLocalModel = other.model;\n      otherLocalTranscodeClass = other.transcodeClass;\n      otherLocalRequestOptions = other.requestOptions;\n      otherLocalPriority = other.priority;\n      otherLocalListenerCount = other.requestListeners != null ? other.requestListeners.size() : 0;\n    }\n\n    // If there's ever a case where synchronization matters for these values, something else has\n    // gone wrong. It indicates that we'er comparing at least one recycled object, which has to be\n    // protected against via other means. None of these values changes aside from object re-use.\n    return localOverrideWidth == otherLocalOverrideWidth\n        && localOverrideHeight == otherLocalOverrideHeight\n        && Util.bothModelsNullEquivalentOrEquals(localModel, otherLocalModel)\n        && localTranscodeClass.equals(otherLocalTranscodeClass)\n        && Util.bothBaseRequestOptionsNullEquivalentOrEquals(\n            localRequestOptions, otherLocalRequestOptions)\n        && localPriority == otherLocalPriority\n        // We do not want to require that RequestListeners implement equals/hashcode, so we\n        // don't compare them using equals(). We can however, at least assert that the same\n        // amount of request listeners are present in both requests.\n        && localListenerCount == otherLocalListenerCount;\n  }\n\n  private void logV(String message) {\n    Log.v(TAG, message + \" this: \" + tag);\n  }\n\n  @Override\n  public String toString() {\n    Object localModel;\n    Class<?> localTranscodeClass;\n    synchronized (requestLock) {\n      localModel = model;\n      localTranscodeClass = transcodeClass;\n    }\n    return super.toString()\n        + \"[model=\"\n        + localModel\n        + \", transcodeClass=\"\n        + localTranscodeClass\n        + \"]\";\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java",
    "content": "package com.bumptech.glide.request;\n\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.Nullable;\n\n/**\n * A coordinator that coordinates two individual {@link Request}s that load a small thumbnail\n * version of an image and the full size version of the image at the same time.\n */\npublic class ThumbnailRequestCoordinator implements RequestCoordinator, Request {\n  @Nullable private final RequestCoordinator parent;\n  private final Object requestLock;\n\n  private volatile Request full;\n  private volatile Request thumb;\n\n  @GuardedBy(\"requestLock\")\n  private RequestState fullState = RequestState.CLEARED;\n\n  @GuardedBy(\"requestLock\")\n  private RequestState thumbState = RequestState.CLEARED;\n\n  // Only used to check if the full request is cleared by the thumbnail request.\n  @GuardedBy(\"requestLock\")\n  private boolean isRunningDuringBegin;\n\n  public ThumbnailRequestCoordinator(Object requestLock, @Nullable RequestCoordinator parent) {\n    this.requestLock = requestLock;\n    this.parent = parent;\n  }\n\n  public void setRequests(Request full, Request thumb) {\n    this.full = full;\n    this.thumb = thumb;\n  }\n\n  /**\n   * Returns true if the request is either the request loading the full size image or if the request\n   * loading the full size image has not yet completed.\n   *\n   * @param request {@inheritDoc}\n   */\n  @Override\n  public boolean canSetImage(Request request) {\n    synchronized (requestLock) {\n      return parentCanSetImage() && (request.equals(full) || fullState != RequestState.SUCCESS);\n    }\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean parentCanSetImage() {\n    return parent == null || parent.canSetImage(this);\n  }\n\n  /**\n   * Returns true if the request is the request loading the full size image and if neither the full\n   * nor the thumbnail image have completed successfully.\n   *\n   * @param request {@inheritDoc}.\n   */\n  @Override\n  public boolean canNotifyStatusChanged(Request request) {\n    synchronized (requestLock) {\n      return parentCanNotifyStatusChanged() && request.equals(full) && !isAnyResourceSet();\n    }\n  }\n\n  @Override\n  public boolean canNotifyCleared(Request request) {\n    synchronized (requestLock) {\n      return parentCanNotifyCleared() && request.equals(full) && fullState != RequestState.PAUSED;\n    }\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean parentCanNotifyCleared() {\n    return parent == null || parent.canNotifyCleared(this);\n  }\n\n  @GuardedBy(\"requestLock\")\n  private boolean parentCanNotifyStatusChanged() {\n    return parent == null || parent.canNotifyStatusChanged(this);\n  }\n\n  @Override\n  public boolean isAnyResourceSet() {\n    synchronized (requestLock) {\n      return thumb.isAnyResourceSet() || full.isAnyResourceSet();\n    }\n  }\n\n  @Override\n  public void onRequestSuccess(Request request) {\n    synchronized (requestLock) {\n      if (request.equals(thumb)) {\n        thumbState = RequestState.SUCCESS;\n        return;\n      }\n      fullState = RequestState.SUCCESS;\n      if (parent != null) {\n        parent.onRequestSuccess(this);\n      }\n      // Clearing the thumb is not necessarily safe if the thumb is being displayed in the Target,\n      // as a layer in a cross fade for example. The only way we know the thumb is not being\n      // displayed and is therefore safe to clear is if the thumb request has not yet completed.\n      if (!thumbState.isComplete()) {\n        thumb.clear();\n      }\n    }\n  }\n\n  @Override\n  public void onRequestFailed(Request request) {\n    synchronized (requestLock) {\n      if (!request.equals(full)) {\n        thumbState = RequestState.FAILED;\n        return;\n      }\n      fullState = RequestState.FAILED;\n\n      if (parent != null) {\n        parent.onRequestFailed(this);\n      }\n    }\n  }\n\n  @Override\n  public RequestCoordinator getRoot() {\n    synchronized (requestLock) {\n      return parent != null ? parent.getRoot() : this;\n    }\n  }\n\n  /** Starts first the thumb request and then the full request. */\n  @Override\n  public void begin() {\n    synchronized (requestLock) {\n      isRunningDuringBegin = true;\n      try {\n        // If the request has completed previously, there's no need to restart both the full and the\n        // thumb, we can just restart the full.\n        if (fullState != RequestState.SUCCESS && thumbState != RequestState.RUNNING) {\n          thumbState = RequestState.RUNNING;\n          thumb.begin();\n        }\n        if (isRunningDuringBegin && fullState != RequestState.RUNNING) {\n          fullState = RequestState.RUNNING;\n          full.begin();\n        }\n      } finally {\n        isRunningDuringBegin = false;\n      }\n    }\n  }\n\n  @Override\n  public void clear() {\n    synchronized (requestLock) {\n      isRunningDuringBegin = false;\n      fullState = RequestState.CLEARED;\n      thumbState = RequestState.CLEARED;\n      thumb.clear();\n      full.clear();\n    }\n  }\n\n  @Override\n  public void pause() {\n    synchronized (requestLock) {\n      if (!thumbState.isComplete()) {\n        thumbState = RequestState.PAUSED;\n        thumb.pause();\n      }\n      if (!fullState.isComplete()) {\n        fullState = RequestState.PAUSED;\n        full.pause();\n      }\n    }\n  }\n\n  @Override\n  public boolean isRunning() {\n    synchronized (requestLock) {\n      return fullState == RequestState.RUNNING;\n    }\n  }\n\n  @Override\n  public boolean isComplete() {\n    synchronized (requestLock) {\n      return fullState == RequestState.SUCCESS;\n    }\n  }\n\n  @Override\n  public boolean isCleared() {\n    synchronized (requestLock) {\n      return fullState == RequestState.CLEARED;\n    }\n  }\n\n  @Override\n  public boolean isEquivalentTo(Request o) {\n    if (o instanceof ThumbnailRequestCoordinator) {\n      ThumbnailRequestCoordinator that = (ThumbnailRequestCoordinator) o;\n      return (full == null ? that.full == null : full.isEquivalentTo(that.full))\n          && (thumb == null ? that.thumb == null : thumb.isEquivalentTo(that.thumb));\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/AppWidgetTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.appwidget.AppWidgetManager;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.widget.RemoteViews;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * This class is used in order to display downloaded Bitmap inside an ImageView of an AppWidget\n * through RemoteViews.\n *\n * <p>Note - For cancellation to work correctly, you must pass in the same instance of this class\n * for every subsequent load.\n */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic class AppWidgetTarget extends CustomTarget<Bitmap> {\n  private final int[] widgetIds;\n  private final ComponentName componentName;\n  private final RemoteViews remoteViews;\n  private final Context context;\n  private final int viewId;\n\n  /**\n   * Constructor using an int array of widgetIds to get a handle on the Widget in order to update\n   * it.\n   *\n   * @param context Context to use in the AppWidgetManager initialization.\n   * @param width Desired width in pixels of the bitmap that will be loaded. (Needs to be manually\n   *     put because of RemoteViews limitations.)\n   * @param height Desired height in pixels of the bitmap that will be loaded. (Needs to be manually\n   *     put because of RemoteViews limitations.)\n   * @param viewId The id of the ImageView view that will load the image.\n   * @param remoteViews RemoteViews object which contains the ImageView that will load the bitmap.\n   * @param widgetIds The int[] that contains the widget ids of an application.\n   */\n  public AppWidgetTarget(\n      Context context,\n      int width,\n      int height,\n      int viewId,\n      RemoteViews remoteViews,\n      int... widgetIds) {\n    super(width, height);\n    if (widgetIds.length == 0) {\n      throw new IllegalArgumentException(\"WidgetIds must have length > 0\");\n    }\n    this.context = Preconditions.checkNotNull(context, \"Context can not be null!\");\n    this.remoteViews =\n        Preconditions.checkNotNull(remoteViews, \"RemoteViews object can not be null!\");\n    this.widgetIds = Preconditions.checkNotNull(widgetIds, \"WidgetIds can not be null!\");\n    this.viewId = viewId;\n    componentName = null;\n  }\n\n  /**\n   * Constructor using an int array of widgetIds to get a handle on the Widget in order to update it\n   * that uses {@link #SIZE_ORIGINAL} as the target width and height.\n   *\n   * @param context Context to use in the AppWidgetManager initialization.\n   * @param viewId The id of the ImageView view that will load the image.\n   * @param remoteViews RemoteViews object which contains the ImageView that will load the bitmap.\n   * @param widgetIds The int[] that contains the widget ids of an application.\n   */\n  public AppWidgetTarget(Context context, int viewId, RemoteViews remoteViews, int... widgetIds) {\n    this(context, SIZE_ORIGINAL, SIZE_ORIGINAL, viewId, remoteViews, widgetIds);\n  }\n\n  /**\n   * Constructor using a ComponentName to get a handle on the Widget in order to update it.\n   *\n   * @param context Context to use in the AppWidgetManager initialization.\n   * @param width Desired width in pixels of the bitmap that will be loaded. (Needs to be manually\n   *     put because of RemoteViews limitations.)\n   * @param height Desired height in pixels of the bitmap that will be loaded. (Needs to be manually\n   *     put because of RemoteViews limitations.)\n   * @param viewId The id of the ImageView view that will load the image.\n   * @param remoteViews RemoteViews object which contains the ImageView that will load the bitmap.\n   * @param componentName The ComponentName that refers to our AppWidget.\n   */\n  public AppWidgetTarget(\n      Context context,\n      int width,\n      int height,\n      int viewId,\n      RemoteViews remoteViews,\n      ComponentName componentName) {\n    super(width, height);\n    this.context = Preconditions.checkNotNull(context, \"Context can not be null!\");\n    this.remoteViews =\n        Preconditions.checkNotNull(remoteViews, \"RemoteViews object can not be null!\");\n    this.componentName =\n        Preconditions.checkNotNull(componentName, \"ComponentName can not be null!\");\n    this.viewId = viewId;\n    widgetIds = null;\n  }\n\n  /**\n   * Constructor using a ComponentName, when override has been put to get a handle on the Widget in\n   * order to update it that uses {@link #SIZE_ORIGINAL} as the target width and height.\n   *\n   * @param context Context to use in the AppWidgetManager initialization.\n   * @param viewId The id of the ImageView view that will load the image.\n   * @param remoteViews RemoteViews object which contains the ImageView that will load the bitmap.\n   * @param componentName The ComponentName that refers to our AppWidget.\n   */\n  public AppWidgetTarget(\n      Context context, int viewId, RemoteViews remoteViews, ComponentName componentName) {\n    this(context, SIZE_ORIGINAL, SIZE_ORIGINAL, viewId, remoteViews, componentName);\n  }\n\n  /** Updates the AppWidget after the ImageView has loaded the Bitmap. */\n  private void update() {\n    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this.context);\n    if (this.componentName != null) {\n      appWidgetManager.updateAppWidget(this.componentName, this.remoteViews);\n    } else {\n      appWidgetManager.updateAppWidget(this.widgetIds, this.remoteViews);\n    }\n  }\n\n  @Override\n  public void onResourceReady(\n      @NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {\n    setBitmap(resource);\n  }\n\n  @Override\n  public void onLoadCleared(@Nullable Drawable placeholder) {\n    setBitmap(null);\n  }\n\n  private void setBitmap(@Nullable Bitmap bitmap) {\n    this.remoteViews.setImageViewBitmap(viewId, bitmap);\n    update();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/BaseTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.request.Request;\n\n/**\n * A base {@link Target} for loading {@link com.bumptech.glide.load.engine.Resource}s that provides\n * basic or empty implementations for most methods.\n *\n * <p>For maximum efficiency, clear this target when you have finished using or displaying the\n * {@link com.bumptech.glide.load.engine.Resource} loaded into it using {@link\n * com.bumptech.glide.RequestManager#clear(Target)}.\n *\n * <p>For loading {@link com.bumptech.glide.load.engine.Resource}s into {@link android.view.View}s,\n * {@link com.bumptech.glide.request.target.ViewTarget} or {@link\n * com.bumptech.glide.request.target.ImageViewTarget} are preferable.\n *\n * @param <Z> The type of resource that will be received by this target.\n * @deprecated Use {@link CustomViewTarget} if loading the content into a view, the download API if\n *     in the background\n *     (http://bumptech.github.io/glide/doc/getting-started.html#background-threads), or a a fully\n *     implemented {@link Target} for any specialized use-cases. Using BaseView is unsafe if the\n *     user does not implement {@link #onLoadCleared}, resulting in recycled bitmaps being\n *     referenced from the UI and hard to root-cause crashes.\n */\n@Deprecated\npublic abstract class BaseTarget<Z> implements Target<Z> {\n\n  private Request request;\n\n  @Override\n  public void setRequest(@Nullable Request request) {\n    this.request = request;\n  }\n\n  @Override\n  @Nullable\n  public Request getRequest() {\n    return request;\n  }\n\n  @Override\n  public void onLoadCleared(@Nullable Drawable placeholder) {\n    // Do nothing.\n  }\n\n  @Override\n  public void onLoadStarted(@Nullable Drawable placeholder) {\n    // Do nothing.\n  }\n\n  @Override\n  public void onLoadFailed(@Nullable Drawable errorDrawable) {\n    // Do nothing.\n  }\n\n  @Override\n  public void onStart() {\n    // Do nothing.\n  }\n\n  @Override\n  public void onStop() {\n    // Do nothing.\n  }\n\n  @Override\n  public void onDestroy() {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/BitmapImageViewTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.Bitmap;\nimport android.widget.ImageView;\n\n/**\n * A {@link com.bumptech.glide.request.target.Target} that can display an {@link\n * android.graphics.Bitmap} in an {@link android.widget.ImageView}.\n */\npublic class BitmapImageViewTarget extends ImageViewTarget<Bitmap> {\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public BitmapImageViewTarget(ImageView view) {\n    super(view);\n  }\n\n  /**\n   * @deprecated Use {@link #waitForLayout()} instead.\n   */\n  // Public API.\n  @SuppressWarnings({\"unused\", \"deprecation\"})\n  @Deprecated\n  public BitmapImageViewTarget(ImageView view, boolean waitForLayout) {\n    super(view, waitForLayout);\n  }\n\n  /**\n   * Sets the {@link android.graphics.Bitmap} on the view using {@link\n   * android.widget.ImageView#setImageBitmap(android.graphics.Bitmap)}.\n   *\n   * @param resource The bitmap to display.\n   */\n  @Override\n  protected void setResource(Bitmap resource) {\n    view.setImageBitmap(resource);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/BitmapThumbnailImageViewTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\n\n/**\n * Efficiently displays multiple Bitmaps loaded serially into a single {@link android.view.View}.\n */\n// Public API.\n@SuppressWarnings(\"unused\")\npublic class BitmapThumbnailImageViewTarget extends ThumbnailImageViewTarget<Bitmap> {\n  public BitmapThumbnailImageViewTarget(ImageView view) {\n    super(view);\n  }\n\n  /**\n   * @deprecated Use {@link #waitForLayout()} instead.\n   */\n  @SuppressWarnings(\"deprecation\")\n  @Deprecated\n  public BitmapThumbnailImageViewTarget(ImageView view, boolean waitForLayout) {\n    super(view, waitForLayout);\n  }\n\n  @Override\n  protected Drawable getDrawable(Bitmap resource) {\n    return new BitmapDrawable(view.getResources(), resource);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/CustomTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Util;\n\n/**\n * A base {@link Target} for loading resources ({@link android.graphics.Bitmap}, {@link Drawable}\n * etc) that are used outside of {@link android.view.View}s.\n *\n * <p>If you're loading a resource into a {@link View}, use {@link\n * com.bumptech.glide.RequestBuilder#into(ImageView)}, a subclass of {@link ImageViewTarget}, or\n * {@link CustomViewTarget}. Using this class to load resources into {@link View}s can prevent Glide\n * from correctly cancelling any previous loads, which may result in incorrect images appearing in\n * the view, especially in scrolling views like {@link androidx.recyclerview.widget.RecyclerView}.\n *\n * <p>You <em>MUST</em> implement {@link #onLoadCleared(Drawable)} and ensure that all references to\n * any resource passed into the target in {@link #onResourceReady(Object, Transition)} are removed\n * before {@link #onLoadCleared(Drawable)} completes. Failing to do so can result in graphical\n * corruption, crashes caused by recycled {@link Bitmap}s, and other undefined behavior. It is never\n * safe to leave {@link #onLoadCleared(Drawable)} unimplemented or empty. Even if you do not\n * manually clear this {@link Target}, Glide may do so automatically after certain lifecycle events\n * in {@link androidx.fragment.app.Fragment}s and {@link android.app.Activity}s.\n *\n * <p>This class can only be used with {@link Target#SIZE_ORIGINAL} or when the desired resource\n * dimensions are known when the {@link Target} is created. If you'd like to run some asynchronous\n * process and make full use of {@link #getSize(SizeReadyCallback)} and {@link SizeReadyCallback},\n * extend {@link Target} directly instead of using this class.\n *\n * @param <T> The type of resource that will be loaded (e.g. {@link Bitmap}).\n */\npublic abstract class CustomTarget<T> implements Target<T> {\n\n  private final int width;\n  private final int height;\n\n  @Nullable private Request request;\n\n  /**\n   * Creates a new {@link CustomTarget} that will attempt to load the resource in its original size.\n   *\n   * <p>This constructor can cause very memory inefficient loads if the resource is large and can\n   * cause OOMs. It's provided as a convenience for when you'd like to specify dimensions with\n   * {@link com.bumptech.glide.request.RequestOptions#override(int)}. In all other cases, prefer\n   * {@link #CustomTarget(int, int)}.\n   */\n  public CustomTarget() {\n    this(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);\n  }\n\n  /**\n   * Creates a new {@code CustomTarget} that will return the given {@code width} and {@code height}\n   * as the requested size (unless overridden by {@link\n   * com.bumptech.glide.request.RequestOptions#override(int)} in the request).\n   *\n   * @param width The requested width in pixels ({@code > 0, or == Target.SIZE_ORIGINAL}).\n   * @param height The requested height in pixels ({@code > 0, or == Target.SIZE_ORIGINAL}).\n   * @throws IllegalArgumentException if width/height doesn't meet the requirement: {@code > 0, or\n   *     == Target.SIZE_ORIGINAL}\n   */\n  public CustomTarget(int width, int height) {\n    if (!Util.isValidDimensions(width, height)) {\n      throw new IllegalArgumentException(\n          \"Width and height must both be > 0 or Target#SIZE_ORIGINAL, but given\"\n              + \" width: \"\n              + width\n              + \" and height: \"\n              + height);\n    }\n\n    this.width = width;\n    this.height = height;\n  }\n\n  @Override\n  public void onStart() {\n    // Intentionally empty, this can be optionally implemented by subclasses.\n  }\n\n  @Override\n  public void onStop() {\n    // Intentionally empty, this can be optionally implemented by subclasses.\n  }\n\n  @Override\n  public void onDestroy() {\n    // Intentionally empty, this can be optionally implemented by subclasses.\n  }\n\n  @Override\n  public void onLoadStarted(@Nullable Drawable placeholder) {\n    // Intentionally empty, this can be optionally implemented by subclasses.\n  }\n\n  @Override\n  public void onLoadFailed(@Nullable Drawable errorDrawable) {\n    // Intentionally empty, this can be optionally implemented by subclasses.\n  }\n\n  @Override\n  public final void getSize(@NonNull SizeReadyCallback cb) {\n    cb.onSizeReady(width, height);\n  }\n\n  @Override\n  public final void removeCallback(@NonNull SizeReadyCallback cb) {\n    // Do nothing, this class does not retain SizeReadyCallbacks.\n  }\n\n  @Override\n  public final void setRequest(@Nullable Request request) {\n    this.request = request;\n  }\n\n  @Nullable\n  @Override\n  public final Request getRequest() {\n    return request;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/CustomViewTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.content.Context;\nimport android.graphics.Point;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport android.view.Display;\nimport android.view.View;\nimport android.view.View.OnAttachStateChangeListener;\nimport android.view.ViewGroup.LayoutParams;\nimport android.view.ViewTreeObserver;\nimport android.view.WindowManager;\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.R;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A base {@link Target} for loading resources ({@link android.graphics.Bitmap}, {@link Drawable}\n * etc) into {@link View}s that provides default implementations for most methods and can determine\n * the size of views using a {@link android.view.ViewTreeObserver.OnDrawListener}.\n *\n * @param <T> The specific subclass of view wrapped by this target (e.g. {@link\n *     android.widget.ImageView})\n * @param <Z> The resource type this target will receive (e.g. {@link android.graphics.Bitmap}).\n */\npublic abstract class CustomViewTarget<T extends View, Z> implements Target<Z> {\n  private static final String TAG = \"CustomViewTarget\";\n  @IdRes private static final int VIEW_TAG_ID = R.id.glide_custom_view_target_tag;\n\n  private final SizeDeterminer sizeDeterminer;\n\n  protected final T view;\n  @Nullable private OnAttachStateChangeListener attachStateListener;\n  private boolean isClearedByUs;\n  private boolean isAttachStateListenerAdded;\n\n  /** Constructor that defaults {@code waitForLayout} to {@code false}. */\n  public CustomViewTarget(@NonNull T view) {\n    this.view = Preconditions.checkNotNull(view);\n    sizeDeterminer = new SizeDeterminer(view);\n  }\n\n  /**\n   * A required callback invoked when the resource is no longer valid and must be freed.\n   *\n   * <p>You must ensure that any current Drawable received in {@link #onResourceReady(Object,\n   * Transition)} is no longer used before redrawing the container (usually a View) or changing its\n   * visibility. <b>Not doing so will result in crashes in your app.</b>\n   *\n   * @param placeholder The placeholder drawable to optionally show, or null.\n   */\n  protected abstract void onResourceCleared(@Nullable Drawable placeholder);\n\n  /**\n   * An optional callback invoked when a resource load is started.\n   *\n   * @see Target#onLoadStarted(Drawable)\n   * @param placeholder The placeholder drawable to optionally show, or null.\n   */\n  protected void onResourceLoading(@Nullable Drawable placeholder) {\n    // Default empty.\n  }\n\n  @Override\n  public void onStart() {\n    // Default empty.\n  }\n\n  @Override\n  public void onStop() {\n    // Default empty.\n  }\n\n  @Override\n  public void onDestroy() {\n    // Default empty.\n  }\n\n  /**\n   * Indicates that Glide should always wait for any pending layout pass before checking for the\n   * size an {@link View}.\n   *\n   * <p>By default, Glide will only wait for a pending layout pass if it's unable to resolve the\n   * size from the {@link LayoutParams} or valid non-zero values for {@link View#getWidth()} and\n   * {@link View#getHeight()}.\n   *\n   * <p>Because calling this method forces Glide to wait for the layout pass to occur before\n   * starting loads, setting this parameter to {@code true} can cause Glide to asynchronous load an\n   * image even if it's in the memory cache. The load will happen asynchronously because Glide has\n   * to wait for a layout pass to occur, which won't necessarily happen in the same frame as when\n   * the image is requested. As a result, using this method can resulting in flashing in some cases\n   * and should be used sparingly.\n   *\n   * <p>If the {@link LayoutParams} of the wrapped {@link View} are set to fixed sizes, they will\n   * still be used instead of the {@link View}'s dimensions even if this method is called. This\n   * parameter is a fallback only.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  public final CustomViewTarget<T, Z> waitForLayout() {\n    sizeDeterminer.waitForLayout = true;\n    return this;\n  }\n\n  /**\n   * Clears the {@link View}'s {@link Request} when the {@link View} is detached from its {@link\n   * android.view.Window} and restarts the {@link Request} when the {@link View} is re-attached from\n   * its {@link android.view.Window}.\n   *\n   * <p>This is an experimental API that may be removed in a future version.\n   *\n   * <p>Using this method can save memory by allowing Glide to more eagerly clear resources when\n   * transitioning screens or swapping adapters in scrolling views. However it also substantially\n   * increases the odds that images will not be in memory if users subsequently return to a screen\n   * where images were previously loaded. Whether or not this happens will depend on the number of\n   * images loaded in the new screen and the size of the memory cache. Increasing the size of the\n   * memory cache can improve this behavior but it largely negates the memory benefits of using this\n   * method.\n   *\n   * <p>Use this method with caution and measure your memory usage to ensure that it's actually\n   * improving your memory usage in the cases you care about.\n   */\n  // Public API.\n  @NonNull\n  @SuppressWarnings({\"UnusedReturnValue\", \"WeakerAccess\"})\n  public final CustomViewTarget<T, Z> clearOnDetach() {\n    if (attachStateListener != null) {\n      return this;\n    }\n    attachStateListener =\n        new OnAttachStateChangeListener() {\n          @Override\n          public void onViewAttachedToWindow(View v) {\n            resumeMyRequest();\n          }\n\n          @Override\n          public void onViewDetachedFromWindow(View v) {\n            pauseMyRequest();\n          }\n        };\n    maybeAddAttachStateListener();\n    return this;\n  }\n\n  /**\n   * Override the android resource id to store temporary state allowing loads to be automatically\n   * cancelled and resources re-used in scrolling lists.\n   *\n   * <p>Unlike {@link ViewTarget}, it is <b>not</b> necessary to set a custom tag id if your app\n   * uses {@link View#setTag(Object)}. It is only necessary if loading several Glide resources into\n   * the same view, for example one foreground and one background view.\n   *\n   * @param tagId The android resource id to use.\n   * @deprecated Using this method prevents clearing the target from working properly. Glide uses\n   *     its own internal tag id so this method should not be necessary. This method is currently a\n   *     no-op.\n   */\n  // Public API.\n  @SuppressWarnings({\"UnusedReturnValue\", \"WeakerAccess\"})\n  @Deprecated\n  public final CustomViewTarget<T, Z> useTagId(@IdRes int tagId) {\n    return this;\n  }\n\n  /** Returns the wrapped {@link android.view.View}. */\n  @NonNull\n  public final T getView() {\n    return view;\n  }\n\n  /**\n   * Determines the size of the view by first checking {@link android.view.View#getWidth()} and\n   * {@link android.view.View#getHeight()}. If one or both are zero, it then checks the view's\n   * {@link LayoutParams}. If one or both of the params width and height are less than or equal to\n   * zero, it then adds an {@link android.view.ViewTreeObserver.OnPreDrawListener} which waits until\n   * the view has been measured before calling the callback with the view's drawn width and height.\n   *\n   * @param cb {@inheritDoc}\n   */\n  @Override\n  public final void getSize(@NonNull SizeReadyCallback cb) {\n    sizeDeterminer.getSize(cb);\n  }\n\n  @Override\n  public final void removeCallback(@NonNull SizeReadyCallback cb) {\n    sizeDeterminer.removeCallback(cb);\n  }\n\n  @Override\n  public final void onLoadStarted(@Nullable Drawable placeholder) {\n    maybeAddAttachStateListener();\n    onResourceLoading(placeholder);\n  }\n\n  @Override\n  public final void onLoadCleared(@Nullable Drawable placeholder) {\n    sizeDeterminer.clearCallbacksAndListener();\n\n    onResourceCleared(placeholder);\n    if (!isClearedByUs) {\n      maybeRemoveAttachStateListener();\n    }\n  }\n\n  /**\n   * Stores the request using {@link View#setTag(Object)}.\n   *\n   * @param request {@inheritDoc}\n   */\n  @Override\n  public final void setRequest(@Nullable Request request) {\n    setTag(request);\n  }\n\n  /** Returns any stored request using {@link android.view.View#getTag()}. */\n  @Override\n  @Nullable\n  public final Request getRequest() {\n    Object tag = getTag();\n    if (tag != null) {\n      if (tag instanceof Request) {\n        return (Request) tag;\n      } else {\n        throw new IllegalArgumentException(\"You must not pass non-R.id ids to setTag(id)\");\n      }\n    }\n    return null;\n  }\n\n  @Override\n  public String toString() {\n    return \"Target for: \" + view;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  final void resumeMyRequest() {\n    Request request = getRequest();\n    if (request != null && request.isCleared()) {\n      request.begin();\n    }\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  final void pauseMyRequest() {\n    Request request = getRequest();\n    if (request != null) {\n      isClearedByUs = true;\n      request.clear();\n      isClearedByUs = false;\n    }\n  }\n\n  private void setTag(@Nullable Object tag) {\n    view.setTag(VIEW_TAG_ID, tag);\n  }\n\n  @Nullable\n  private Object getTag() {\n    return view.getTag(VIEW_TAG_ID);\n  }\n\n  private void maybeAddAttachStateListener() {\n    if (attachStateListener == null || isAttachStateListenerAdded) {\n      return;\n    }\n\n    view.addOnAttachStateChangeListener(attachStateListener);\n    isAttachStateListenerAdded = true;\n  }\n\n  private void maybeRemoveAttachStateListener() {\n    if (attachStateListener == null || !isAttachStateListenerAdded) {\n      return;\n    }\n\n    view.removeOnAttachStateChangeListener(attachStateListener);\n    isAttachStateListenerAdded = false;\n  }\n\n  @VisibleForTesting\n  static final class SizeDeterminer {\n    // Some negative sizes (Target.SIZE_ORIGINAL) are valid, 0 is never valid.\n    private static final int PENDING_SIZE = 0;\n    @VisibleForTesting @Nullable static Integer maxDisplayLength;\n    private final View view;\n    private final List<SizeReadyCallback> cbs = new ArrayList<>();\n    @Synthetic boolean waitForLayout;\n\n    @Nullable private SizeDeterminerLayoutListener layoutListener;\n\n    SizeDeterminer(@NonNull View view) {\n      this.view = view;\n    }\n\n    // Use the maximum to avoid depending on the device's current orientation.\n    private static int getMaxDisplayLength(@NonNull Context context) {\n      if (maxDisplayLength == null) {\n        WindowManager windowManager =\n            (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n        Display display = Preconditions.checkNotNull(windowManager).getDefaultDisplay();\n        Point displayDimensions = new Point();\n        display.getSize(displayDimensions);\n        maxDisplayLength = Math.max(displayDimensions.x, displayDimensions.y);\n      }\n      return maxDisplayLength;\n    }\n\n    private void notifyCbs(int width, int height) {\n      // One or more callbacks may trigger the removal of one or more additional callbacks, so we\n      // need a copy of the list to avoid a concurrent modification exception. One place this\n      // happens is when a full request completes from the in memory cache while its thumbnail is\n      // still being loaded asynchronously. See #2237.\n      for (SizeReadyCallback cb : new ArrayList<>(cbs)) {\n        cb.onSizeReady(width, height);\n      }\n    }\n\n    @Synthetic\n    void checkCurrentDimens() {\n      if (cbs.isEmpty()) {\n        return;\n      }\n\n      int currentWidth = getTargetWidth();\n      int currentHeight = getTargetHeight();\n      if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {\n        return;\n      }\n\n      notifyCbs(currentWidth, currentHeight);\n      clearCallbacksAndListener();\n    }\n\n    void getSize(@NonNull SizeReadyCallback cb) {\n      int currentWidth = getTargetWidth();\n      int currentHeight = getTargetHeight();\n      if (isViewStateAndSizeValid(currentWidth, currentHeight)) {\n        cb.onSizeReady(currentWidth, currentHeight);\n        return;\n      }\n\n      // We want to notify callbacks in the order they were added and we only expect one or two\n      // callbacks to be added a time, so a List is a reasonable choice.\n      if (!cbs.contains(cb)) {\n        cbs.add(cb);\n      }\n      if (layoutListener == null) {\n        ViewTreeObserver observer = view.getViewTreeObserver();\n        layoutListener = new SizeDeterminerLayoutListener(this);\n        observer.addOnPreDrawListener(layoutListener);\n      }\n    }\n\n    /**\n     * The callback may be called anyway if it is removed by another {@link SizeReadyCallback} or\n     * otherwise removed while we're notifying the list of callbacks.\n     *\n     * <p>See #2237.\n     */\n    void removeCallback(@NonNull SizeReadyCallback cb) {\n      cbs.remove(cb);\n    }\n\n    void clearCallbacksAndListener() {\n      // Keep a reference to the layout attachStateListener and remove it here\n      // rather than having the observer remove itself because the observer\n      // we add the attachStateListener to will be almost immediately merged into\n      // another observer and will therefore never be alive. If we instead\n      // keep a reference to the attachStateListener and remove it here, we get the\n      // current view tree observer and should succeed.\n      ViewTreeObserver observer = view.getViewTreeObserver();\n      if (observer.isAlive()) {\n        observer.removeOnPreDrawListener(layoutListener);\n      }\n      layoutListener = null;\n      cbs.clear();\n    }\n\n    private boolean isViewStateAndSizeValid(int width, int height) {\n      return isDimensionValid(width) && isDimensionValid(height);\n    }\n\n    private int getTargetHeight() {\n      int verticalPadding = view.getPaddingTop() + view.getPaddingBottom();\n      LayoutParams layoutParams = view.getLayoutParams();\n      int layoutParamSize = layoutParams != null ? layoutParams.height : PENDING_SIZE;\n      return getTargetDimen(view.getHeight(), layoutParamSize, verticalPadding);\n    }\n\n    private int getTargetWidth() {\n      int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();\n      LayoutParams layoutParams = view.getLayoutParams();\n      int layoutParamSize = layoutParams != null ? layoutParams.width : PENDING_SIZE;\n      return getTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding);\n    }\n\n    private int getTargetDimen(int viewSize, int paramSize, int paddingSize) {\n      // We consider the View state as valid if the View has non-null layout params and a non-zero\n      // layout params width and height. This is imperfect. We're making an assumption that View\n      // parents will obey their child's layout parameters, which isn't always the case.\n      int adjustedParamSize = paramSize - paddingSize;\n      if (adjustedParamSize > 0) {\n        return adjustedParamSize;\n      }\n\n      // Since we always prefer layout parameters with fixed sizes, even if waitForLayout is true,\n      // we might as well ignore it and just return the layout parameters above if we have them.\n      // Otherwise we should wait for a layout pass before checking the View's dimensions.\n      if (waitForLayout && view.isLayoutRequested()) {\n        return PENDING_SIZE;\n      }\n\n      // We also consider the View state valid if the View has a non-zero width and height. This\n      // means that the View has gone through at least one layout pass. It does not mean the Views\n      // width and height are from the current layout pass. For example, if a View is re-used in\n      // RecyclerView or ListView, this width/height may be from an old position. In some cases\n      // the dimensions of the View at the old position may be different than the dimensions of the\n      // View in the new position because the LayoutManager/ViewParent can arbitrarily decide to\n      // change them. Nevertheless, in most cases this should be a reasonable choice.\n      int adjustedViewSize = viewSize - paddingSize;\n      if (adjustedViewSize > 0) {\n        return adjustedViewSize;\n      }\n\n      // Finally we consider the view valid if the layout parameter size is set to wrap_content.\n      // It's difficult for Glide to figure out what to do here. Although Target.SIZE_ORIGINAL is a\n      // coherent choice, it's extremely dangerous because original images may be much too large to\n      // fit in memory or so large that only a couple can fit in memory, causing OOMs. If users want\n      // the original image, they can always use .override(Target.SIZE_ORIGINAL). Since wrap_content\n      // may never resolve to a real size unless we load something, we aim for a square whose length\n      // is the largest screen size. That way we're loading something and that something has some\n      // hope of being downsampled to a size that the device can support. We also log a warning that\n      // tries to explain what Glide is doing and why some alternatives are preferable.\n      // Since WRAP_CONTENT is sometimes used as a default layout parameter, we always wait for\n      // layout to complete before using this fallback parameter (ConstraintLayout among others).\n      if (!view.isLayoutRequested() && paramSize == LayoutParams.WRAP_CONTENT) {\n        if (Log.isLoggable(TAG, Log.INFO)) {\n          Log.i(\n              TAG,\n              \"Glide treats LayoutParams.WRAP_CONTENT as a request for an image the size of\"\n                  + \" this device's screen dimensions. If you want to load the original image and\"\n                  + \" are ok with the corresponding memory cost and OOMs (depending on the input\"\n                  + \" size), use .override(Target.SIZE_ORIGINAL). Otherwise, use\"\n                  + \" LayoutParams.MATCH_PARENT, set layout_width and layout_height to fixed\"\n                  + \" dimension, or use .override() with fixed dimensions.\");\n        }\n        return getMaxDisplayLength(view.getContext());\n      }\n\n      // If the layout parameters are < padding, the view size is < padding, or the layout\n      // parameters are set to match_parent or wrap_content and no layout has occurred, we should\n      // wait for layout and repeat.\n      return PENDING_SIZE;\n    }\n\n    private boolean isDimensionValid(int size) {\n      return size > 0 || size == SIZE_ORIGINAL;\n    }\n\n    private static final class SizeDeterminerLayoutListener\n        implements ViewTreeObserver.OnPreDrawListener {\n      private final WeakReference<SizeDeterminer> sizeDeterminerRef;\n\n      SizeDeterminerLayoutListener(@NonNull SizeDeterminer sizeDeterminer) {\n        sizeDeterminerRef = new WeakReference<>(sizeDeterminer);\n      }\n\n      @Override\n      public boolean onPreDraw() {\n        if (Log.isLoggable(TAG, Log.VERBOSE)) {\n          Log.v(TAG, \"OnGlobalLayoutListener called attachStateListener=\" + this);\n        }\n        SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();\n        if (sizeDeterminer != null) {\n          sizeDeterminer.checkCurrentDimens();\n        }\n        return true;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/DrawableImageViewTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\nimport androidx.annotation.Nullable;\n\n/** A target for display {@link Drawable} objects in {@link ImageView}s. */\npublic class DrawableImageViewTarget extends ImageViewTarget<Drawable> {\n\n  public DrawableImageViewTarget(ImageView view) {\n    super(view);\n  }\n\n  /**\n   * @deprecated Use {@link #waitForLayout()} instead.\n   */\n  // Public API.\n  @SuppressWarnings({\"unused\", \"deprecation\"})\n  @Deprecated\n  public DrawableImageViewTarget(ImageView view, boolean waitForLayout) {\n    super(view, waitForLayout);\n  }\n\n  @Override\n  protected void setResource(@Nullable Drawable resource) {\n    view.setImageDrawable(resource);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/DrawableThumbnailImageViewTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\n\n/**\n * Efficiently displays multiple Drawables loaded serially into a single {@link android.view.View}.\n */\n// Public API.\n@SuppressWarnings(\"unused\")\npublic class DrawableThumbnailImageViewTarget extends ThumbnailImageViewTarget<Drawable> {\n  public DrawableThumbnailImageViewTarget(ImageView view) {\n    super(view);\n  }\n\n  /**\n   * @deprecated Use {@link #waitForLayout()} instead.\n   */\n  @Deprecated\n  @SuppressWarnings(\"deprecation\")\n  public DrawableThumbnailImageViewTarget(ImageView view, boolean waitForLayout) {\n    super(view, waitForLayout);\n  }\n\n  @Override\n  protected Drawable getDrawable(Drawable resource) {\n    return resource;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/FixedSizeDrawable.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.content.res.Resources;\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.Matrix;\nimport android.graphics.PorterDuff;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\n\n/**\n * A wrapper drawable to square the wrapped drawable so that it expands to fill a square with\n * exactly the given side length. The goal of this drawable is to ensure that square thumbnail\n * drawables always match the size of the view they will be displayed in to avoid a costly\n * requestLayout call. This class should not be used with views or drawables that are not square.\n */\npublic class FixedSizeDrawable extends Drawable {\n  private final Matrix matrix;\n  private final RectF wrappedRect;\n  private final RectF bounds;\n  private Drawable wrapped;\n  private State state;\n  private boolean mutated;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public FixedSizeDrawable(Drawable wrapped, int width, int height) {\n    this(new State(wrapped.getConstantState(), width, height), wrapped);\n  }\n\n  FixedSizeDrawable(State state, Drawable wrapped) {\n    this.state = Preconditions.checkNotNull(state);\n    this.wrapped = Preconditions.checkNotNull(wrapped);\n\n    // We will do our own scaling.\n    wrapped.setBounds(0, 0, wrapped.getIntrinsicWidth(), wrapped.getIntrinsicHeight());\n\n    matrix = new Matrix();\n    wrappedRect = new RectF(0, 0, wrapped.getIntrinsicWidth(), wrapped.getIntrinsicHeight());\n    bounds = new RectF();\n  }\n\n  @Override\n  public void setBounds(int left, int top, int right, int bottom) {\n    super.setBounds(left, top, right, bottom);\n    bounds.set(left, top, right, bottom);\n    updateMatrix();\n  }\n\n  @Override\n  public void setBounds(@NonNull Rect bounds) {\n    super.setBounds(bounds);\n    this.bounds.set(bounds);\n    updateMatrix();\n  }\n\n  private void updateMatrix() {\n    matrix.setRectToRect(wrappedRect, this.bounds, Matrix.ScaleToFit.CENTER);\n  }\n\n  @Override\n  public void setChangingConfigurations(int configs) {\n    wrapped.setChangingConfigurations(configs);\n  }\n\n  @Override\n  public int getChangingConfigurations() {\n    return wrapped.getChangingConfigurations();\n  }\n\n  @Deprecated\n  @Override\n  public void setDither(boolean dither) {\n    wrapped.setDither(dither);\n  }\n\n  @Override\n  public void setFilterBitmap(boolean filter) {\n    wrapped.setFilterBitmap(filter);\n  }\n\n  @Override\n  public Callback getCallback() {\n    return wrapped.getCallback();\n  }\n\n  @RequiresApi(Build.VERSION_CODES.KITKAT)\n  @Override\n  public int getAlpha() {\n    return wrapped.getAlpha();\n  }\n\n  @Override\n  public void setColorFilter(int color, @NonNull PorterDuff.Mode mode) {\n    wrapped.setColorFilter(color, mode);\n  }\n\n  @Override\n  public void clearColorFilter() {\n    wrapped.clearColorFilter();\n  }\n\n  @NonNull\n  @Override\n  public Drawable getCurrent() {\n    return wrapped.getCurrent();\n  }\n\n  @Override\n  public boolean setVisible(boolean visible, boolean restart) {\n    return wrapped.setVisible(visible, restart);\n  }\n\n  @Override\n  public int getIntrinsicWidth() {\n    return state.width;\n  }\n\n  @Override\n  public int getIntrinsicHeight() {\n    return state.height;\n  }\n\n  @Override\n  public int getMinimumWidth() {\n    return wrapped.getMinimumWidth();\n  }\n\n  @Override\n  public int getMinimumHeight() {\n    return wrapped.getMinimumHeight();\n  }\n\n  @Override\n  public boolean getPadding(@NonNull Rect padding) {\n    return wrapped.getPadding(padding);\n  }\n\n  @Override\n  public void invalidateSelf() {\n    super.invalidateSelf();\n    wrapped.invalidateSelf();\n  }\n\n  @Override\n  public void unscheduleSelf(@NonNull Runnable what) {\n    super.unscheduleSelf(what);\n    wrapped.unscheduleSelf(what);\n  }\n\n  @Override\n  public void scheduleSelf(@NonNull Runnable what, long when) {\n    super.scheduleSelf(what, when);\n    wrapped.scheduleSelf(what, when);\n  }\n\n  @Override\n  public void draw(@NonNull Canvas canvas) {\n    canvas.save();\n    canvas.concat(matrix);\n    wrapped.draw(canvas);\n    canvas.restore();\n  }\n\n  @Override\n  public void setAlpha(int i) {\n    wrapped.setAlpha(i);\n  }\n\n  @Override\n  public void setColorFilter(ColorFilter colorFilter) {\n    wrapped.setColorFilter(colorFilter);\n  }\n\n  @Override\n  public int getOpacity() {\n    return wrapped.getOpacity();\n  }\n\n  @NonNull\n  @Override\n  public Drawable mutate() {\n    if (!mutated && super.mutate() == this) {\n      wrapped = wrapped.mutate();\n      state = new State(state);\n      mutated = true;\n    }\n    return this;\n  }\n\n  @Override\n  public ConstantState getConstantState() {\n    return state;\n  }\n\n  static final class State extends ConstantState {\n    private final ConstantState wrapped;\n    @Synthetic final int width;\n    @Synthetic final int height;\n\n    State(State other) {\n      this(other.wrapped, other.width, other.height);\n    }\n\n    State(ConstantState wrapped, int width, int height) {\n      this.wrapped = wrapped;\n      this.width = width;\n      this.height = height;\n    }\n\n    @NonNull\n    @Override\n    public Drawable newDrawable() {\n      return new FixedSizeDrawable(this, wrapped.newDrawable());\n    }\n\n    @NonNull\n    @Override\n    public Drawable newDrawable(Resources res) {\n      return new FixedSizeDrawable(this, wrapped.newDrawable(res));\n    }\n\n    @Override\n    public int getChangingConfigurations() {\n      return 0;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/ImageViewTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.drawable.Animatable;\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.request.transition.Transition;\n\n/**\n * A base {@link com.bumptech.glide.request.target.Target} for displaying resources in {@link\n * android.widget.ImageView}s.\n *\n * @param <Z> The type of resource that this target will display in the wrapped {@link\n *     android.widget.ImageView}.\n */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z>\n    implements Transition.ViewAdapter {\n\n  @Nullable private Animatable animatable;\n\n  public ImageViewTarget(ImageView view) {\n    super(view);\n  }\n\n  /**\n   * @deprecated Use {@link #waitForLayout()} instead.\n   */\n  @SuppressWarnings({\"deprecation\"})\n  @Deprecated\n  public ImageViewTarget(ImageView view, boolean waitForLayout) {\n    super(view, waitForLayout);\n  }\n\n  /**\n   * Returns the current {@link android.graphics.drawable.Drawable} being displayed in the view\n   * using {@link android.widget.ImageView#getDrawable()}.\n   */\n  @Override\n  @Nullable\n  public Drawable getCurrentDrawable() {\n    return view.getDrawable();\n  }\n\n  /**\n   * Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link\n   * android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.\n   *\n   * @param drawable {@inheritDoc}\n   */\n  @Override\n  public void setDrawable(Drawable drawable) {\n    view.setImageDrawable(drawable);\n  }\n\n  /**\n   * Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link\n   * android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.\n   *\n   * @param placeholder {@inheritDoc}\n   */\n  @Override\n  public void onLoadStarted(@Nullable Drawable placeholder) {\n    super.onLoadStarted(placeholder);\n    setResourceInternal(null);\n    setDrawable(placeholder);\n  }\n\n  /**\n   * Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link\n   * android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.\n   *\n   * @param errorDrawable {@inheritDoc}\n   */\n  @Override\n  public void onLoadFailed(@Nullable Drawable errorDrawable) {\n    super.onLoadFailed(errorDrawable);\n    setResourceInternal(null);\n    setDrawable(errorDrawable);\n  }\n\n  /**\n   * Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link\n   * android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.\n   *\n   * @param placeholder {@inheritDoc}\n   */\n  @Override\n  public void onLoadCleared(@Nullable Drawable placeholder) {\n    super.onLoadCleared(placeholder);\n    if (animatable != null) {\n      animatable.stop();\n    }\n    setResourceInternal(null);\n    setDrawable(placeholder);\n  }\n\n  @Override\n  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {\n    if (transition == null || !transition.transition(resource, this)) {\n      setResourceInternal(resource);\n    } else {\n      maybeUpdateAnimatable(resource);\n    }\n  }\n\n  @Override\n  public void onStart() {\n    if (animatable != null) {\n      animatable.start();\n    }\n  }\n\n  @Override\n  public void onStop() {\n    if (animatable != null) {\n      animatable.stop();\n    }\n  }\n\n  private void setResourceInternal(@Nullable Z resource) {\n    // Order matters here. Set the resource first to make sure that the Drawable has a valid and\n    // non-null Callback before starting it.\n    setResource(resource);\n    maybeUpdateAnimatable(resource);\n  }\n\n  private void maybeUpdateAnimatable(@Nullable Z resource) {\n    if (resource instanceof Animatable) {\n      animatable = (Animatable) resource;\n      animatable.start();\n    } else {\n      animatable = null;\n    }\n  }\n\n  protected abstract void setResource(@Nullable Z resource);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/ImageViewTargetFactory.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\n\n/**\n * A factory responsible for producing the correct type of {@link\n * com.bumptech.glide.request.target.Target} for a given {@link android.view.View} subclass.\n */\npublic class ImageViewTargetFactory {\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  public <Z> ViewTarget<ImageView, Z> buildTarget(\n      @NonNull ImageView view, @NonNull Class<Z> clazz) {\n    if (Bitmap.class.equals(clazz)) {\n      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);\n    } else if (Drawable.class.isAssignableFrom(clazz)) {\n      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);\n    } else {\n      throw new IllegalArgumentException(\n          \"Unhandled class: \" + clazz + \", try .as*(Class).transcode(ResourceTranscoder)\");\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/NotificationTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.Manifest;\nimport android.annotation.SuppressLint;\nimport android.app.Notification;\nimport android.app.NotificationManager;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.widget.RemoteViews;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresPermission;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * This class is used to display downloaded Bitmap inside an ImageView of a Notification through\n * RemoteViews.\n *\n * <p>Note - For cancellation to work correctly, you must pass in the same instance of this class\n * for every subsequent load.\n */\n// Public API.\n@SuppressWarnings({\"WeakerAccess\", \"unused\"})\npublic class NotificationTarget extends CustomTarget<Bitmap> {\n  private final RemoteViews remoteViews;\n  private final Context context;\n  private final int notificationId;\n  private final String notificationTag;\n  private final Notification notification;\n  private final int viewId;\n\n  /**\n   * Constructor using a Notification object and a notificationId to get a handle on the\n   * Notification in order to update it that uses {@link #SIZE_ORIGINAL} as the target width and\n   * height.\n   *\n   * @param context Context to use in the AppWidgetManager initialization.\n   * @param viewId The id of the ImageView view that will load the image.\n   * @param remoteViews RemoteViews object which contains the ImageView that will load the bitmap.\n   * @param notification The Notification object that we want to update.\n   * @param notificationId The notificationId of the Notification that we want to load the Bitmap.\n   */\n  @SuppressLint(\"InlinedApi\")\n  // Alert users of Glide to have this permission.\n  @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n  public NotificationTarget(\n      Context context,\n      int viewId,\n      RemoteViews remoteViews,\n      Notification notification,\n      int notificationId) {\n    this(context, viewId, remoteViews, notification, notificationId, null);\n  }\n\n  /**\n   * Constructor using a Notification object, a notificationId, and a notificationTag to get a\n   * handle on the Notification in order to update it that uses {@link #SIZE_ORIGINAL} as the target\n   * width and height.\n   *\n   * @param context Context to use in the AppWidgetManager initialization.\n   * @param viewId The id of the ImageView view that will load the image.\n   * @param remoteViews RemoteViews object which contains the ImageView that will load the bitmap.\n   * @param notification The Notification object that we want to update.\n   * @param notificationId The notificationId of the Notification that we want to load the Bitmap.\n   * @param notificationTag The notificationTag of the Notification that we want to load the Bitmap.\n   *     May be {@code null}.\n   */\n  @SuppressLint(\"InlinedApi\")\n  // Alert users of Glide to have this permission.\n  @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n  public NotificationTarget(\n      Context context,\n      int viewId,\n      RemoteViews remoteViews,\n      Notification notification,\n      int notificationId,\n      String notificationTag) {\n    this(\n        context,\n        SIZE_ORIGINAL,\n        SIZE_ORIGINAL,\n        viewId,\n        remoteViews,\n        notification,\n        notificationId,\n        notificationTag);\n  }\n\n  /**\n   * Constructor using a Notification object, a notificationId, and a notificationTag to get a\n   * handle on the Notification in order to update it.\n   *\n   * @param context Context to use in the AppWidgetManager initialization.\n   * @param width Desired width of the bitmap that will be loaded.(Need to be manually put because\n   *     of RemoteViews limitations.)\n   * @param height Desired height of the bitmap that will be loaded. (Need to be manually put\n   *     because of RemoteViews limitations.)\n   * @param viewId The id of the ImageView view that will load the image.\n   * @param remoteViews RemoteViews object which contains the ImageView that will load the bitmap.\n   * @param notification The Notification object that we want to update.\n   * @param notificationId The notificationId of the Notification that we want to load the Bitmap.\n   * @param notificationTag The notificationTag of the Notification that we want to load the Bitmap.\n   *     May be {@code null}.\n   */\n  @SuppressLint(\"InlinedApi\")\n  // Alert users of Glide to have this permission.\n  @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n  public NotificationTarget(\n      Context context,\n      int width,\n      int height,\n      int viewId,\n      RemoteViews remoteViews,\n      Notification notification,\n      int notificationId,\n      String notificationTag) {\n    super(width, height);\n    this.context = Preconditions.checkNotNull(context, \"Context must not be null!\");\n    this.notification =\n        Preconditions.checkNotNull(notification, \"Notification object can not be null!\");\n    this.remoteViews =\n        Preconditions.checkNotNull(remoteViews, \"RemoteViews object can not be null!\");\n    this.viewId = viewId;\n    this.notificationId = notificationId;\n    this.notificationTag = notificationTag;\n  }\n\n  /** Updates the Notification after the Bitmap resource is loaded. */\n  @SuppressLint(\"InlinedApi\")\n  // Help tools to recognize that this method requires a permission, because it posts a\n  // notification.\n  @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n  private void update() {\n    NotificationManager manager =\n        (NotificationManager) this.context.getSystemService(Context.NOTIFICATION_SERVICE);\n    Preconditions.checkNotNull(manager)\n        .notify(this.notificationTag, this.notificationId, this.notification);\n  }\n\n  @SuppressLint(\"InlinedApi\")\n  // Help tools to recognize that this method requires a permission, because it calls setBitmap().\n  @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n  @Override\n  public void onResourceReady(\n      @NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {\n    setBitmap(resource);\n  }\n\n  @SuppressLint(\"InlinedApi\")\n  // Help tools to recognize that this method requires a permission, because it calls setBitmap().\n  @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n  @Override\n  public void onLoadCleared(@Nullable Drawable placeholder) {\n    setBitmap(null);\n  }\n\n  @SuppressLint(\"InlinedApi\")\n  // Help tools to recognize that this method requires a permission, because it calls update().\n  @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n  private void setBitmap(@Nullable Bitmap bitmap) {\n    this.remoteViews.setImageViewBitmap(this.viewId, bitmap);\n    this.update();\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.drawable.Drawable;\nimport android.os.Handler;\nimport android.os.Handler.Callback;\nimport android.os.Looper;\nimport android.os.Message;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Synthetic;\n\n/**\n * A one time use {@link com.bumptech.glide.request.target.Target} class that loads a resource into\n * memory and then clears itself.\n *\n * @param <Z> The type of resource that will be loaded into memory.\n */\npublic final class PreloadTarget<Z> extends CustomTarget<Z> {\n  private static final int MESSAGE_CLEAR = 1;\n  private static final Handler HANDLER =\n      new Handler(\n          Looper.getMainLooper(),\n          new Callback() {\n            @Override\n            public boolean handleMessage(Message message) {\n              if (message.what == MESSAGE_CLEAR) {\n                ((PreloadTarget<?>) message.obj).clear();\n                return true;\n              }\n              return false;\n            }\n          });\n\n  private final RequestManager requestManager;\n\n  /**\n   * Returns a PreloadTarget.\n   *\n   * @param width The width in pixels of the desired resource.\n   * @param height The height in pixels of the desired resource.\n   * @param <Z> The type of the desired resource.\n   */\n  public static <Z> PreloadTarget<Z> obtain(RequestManager requestManager, int width, int height) {\n    return new PreloadTarget<>(requestManager, width, height);\n  }\n\n  private PreloadTarget(RequestManager requestManager, int width, int height) {\n    super(width, height);\n    this.requestManager = requestManager;\n  }\n\n  @Override\n  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {\n    // If a thumbnail request is set and the thumbnail completes, we don't want to cancel the\n    // primary load. Instead we wait until the primary request (the one set on the target) says\n    // that it is complete.\n    // Note - Any thumbnail request that does not complete before the primary request will be\n    // cancelled and may not be preloaded successfully. Cancellation of outstanding thumbnails after\n    // the primary request succeeds is a common behavior of all Glide requests and we're not trying\n    // to override it here.\n    Request request = getRequest();\n    if (request != null && request.isComplete()) {\n      HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget();\n    }\n  }\n\n  @Override\n  public void onLoadCleared(@Nullable Drawable placeholder) {\n    // Do nothing, we don't retain a reference to our resource.\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  void clear() {\n    requestManager.clear(this);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/SimpleTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.util.Util;\n\n/**\n * A simple {@link com.bumptech.glide.request.target.Target} base class with default (usually no-op)\n * implementations of non essential methods that allows the caller to specify an exact width/height.\n * Typically use cases look something like this:\n *\n * <pre>{@code\n * Target<Bitmap> target =\n *     Glide.with(fragment)\n *       .asBitmap()\n *       .load(\"http://somefakeurl.com/fakeImage.jpeg\")\n *       .apply(fitCenterTransform())\n *       .into(new SimpleTarget<Bitmap>(250, 250) {\n *\n *         {@literal @Override}\n *         public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {\n *           // Do something with bitmap here.\n *         }\n *\n *       });\n * }\n * // At some later point, clear the Target to release the resources, prevent load queues from\n * // blowing out proportion, and to improve load times for future requests:\n * Glide.with(fragment).clear(target);\n * }</pre>\n *\n * <p><em>Warning!</em> this class is extremely prone to mis-use. Use SimpleTarget only as a last\n * resort. {@link ViewTarget} or a subclass of {@link ViewTarget} is almost always a better choice.\n *\n * <p><em>Don't forget to clear instances of this class!</em>. If you must use this class, keep in\n * mind that unlike {@link ViewTarget} it is not safe to load into new instances of this class\n * repeatedly if every instance updates the same underlying {@link View} or caller. If you need to\n * load into the same {@link View} or caller repeatedly using this class, always retain a reference\n * to the previous instance and either call {@link com.bumptech.glide.RequestManager#clear(Target)}\n * on the old instance before starting a new load or you must re-use the old instance for the new\n * load. Glide's {@link com.bumptech.glide.RequestBuilder#into(Target)} method returns the {@link\n * Target} instance you provided to make retaining a reference to the {@link Target} as easy as\n * possible. That said, you must wait until you're completely finished with the resource before\n * calling {@link com.bumptech.glide.RequestManager#clear(Target)} and you should always null out\n * references to any loaded resources in {@link Target#onLoadCleared(Drawable)}.\n *\n * <p>Always try to provide a size when using this class. Use {@link SimpleTarget#SimpleTarget(int,\n * int)} whenever possible with values that are <em>not</em> {@link Target#SIZE_ORIGINAL}. Using\n * {@link Target#SIZE_ORIGINAL} is unsafe if you're loading large images or are running your\n * application on older or memory constrained devices because it can cause Glide to load very large\n * images into memory. In some cases those images may throw {@link OutOfMemoryError} and in others\n * they may exceed the texture limit for the device, which will prevent them from being rendered.\n * Providing a valid size allows Glide to downsample large images, which can avoid issues with\n * texture size or memory limitations. You don't have to worry about providing a size in most cases\n * if you use {@link ViewTarget} so prefer {@link ViewTarget} over this class whenver possible.\n *\n * @see <a href=\"http://bumptech.github.io/glide/doc/targets.html\">Glide's Target docs page</a>\n * @param <Z> The type of resource that this target will receive.\n * @deprecated Use {@link CustomViewTarget} if loading the content into a view, the download API if\n *     in the background\n *     (http://bumptech.github.io/glide/doc/getting-started.html#background-threads), or a {@link\n *     CustomTarget} for any specialized use-cases. Using {@link SimpleTarget} or {@link BaseTarget}\n *     is unsafe if the user does not implement {@link #onLoadCleared}, resulting in recycled\n *     bitmaps being referenced from the UI and hard to root-cause crashes.\n */\n@Deprecated\npublic abstract class SimpleTarget<Z> extends BaseTarget<Z> {\n  private final int width;\n  private final int height;\n\n  /**\n   * Constructor for the target that uses {@link Target#SIZE_ORIGINAL} as the target width and\n   * height.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public SimpleTarget() {\n    this(SIZE_ORIGINAL, SIZE_ORIGINAL);\n  }\n\n  /**\n   * Constructor for the target that takes the desired dimensions of the decoded and/or transformed\n   * resource.\n   *\n   * @param width The width in pixels of the desired resource.\n   * @param height The height in pixels of the desired resource.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public SimpleTarget(int width, int height) {\n    this.width = width;\n    this.height = height;\n  }\n\n  /**\n   * Immediately calls the given callback with the sizes given in the constructor.\n   *\n   * @param cb {@inheritDoc}\n   */\n  @Override\n  public final void getSize(@NonNull SizeReadyCallback cb) {\n    if (!Util.isValidDimensions(width, height)) {\n      throw new IllegalArgumentException(\n          \"Width and height must both be > 0 or Target#SIZE_ORIGINAL, but given\"\n              + \" width: \"\n              + width\n              + \" and height: \"\n              + height\n              + \", either provide dimensions in the constructor\"\n              + \" or call override()\");\n    }\n    cb.onSizeReady(width, height);\n  }\n\n  @Override\n  public void removeCallback(@NonNull SizeReadyCallback cb) {\n    // Do nothing, we never retain a reference to the callback.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/SizeReadyCallback.java",
    "content": "package com.bumptech.glide.request.target;\n\n/**\n * A callback that must be called when the target has determined its size. For fixed size targets it\n * can be called synchronously.\n */\npublic interface SizeReadyCallback {\n  /**\n   * A callback called on the main thread.\n   *\n   * @param width The width in pixels of the target, or {@link Target#SIZE_ORIGINAL} to indicate\n   *     that we want the resource at its original width.\n   * @param height The height in pixels of the target, or {@link Target#SIZE_ORIGINAL} to indicate\n   *     that we want the resource at its original height.\n   */\n  void onSizeReady(int width, int height);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/Target.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.manager.LifecycleListener;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.transition.Transition;\n\n/**\n * An interface that Glide can load a resource into and notify of relevant lifecycle events during a\n * load.\n *\n * <p>The lifecycle events in this class are as follows:\n *\n * <ul>\n *   <li>onLoadStarted\n *   <li>onResourceReady\n *   <li>onLoadCleared\n *   <li>onLoadFailed\n * </ul>\n *\n * <p>The typical lifecycle is onLoadStarted, then onResourceReady or onLoadFailed, then\n * onLoadCleared. However, there are no guarantees. onLoadStarted may not be called if the resource\n * is in memory or if the load will fail because of a null model object. onLoadCleared similarly may\n * never be called if the target is never cleared. See the docs for the individual methods for\n * details.\n *\n * @param <R> The type of resource the target can display.\n */\npublic interface Target<R> extends LifecycleListener {\n  /** Indicates that we want the resource in its original unmodified width and/or height. */\n  int SIZE_ORIGINAL = Integer.MIN_VALUE;\n\n  /**\n   * A lifecycle callback that is called when a load is started.\n   *\n   * <p>Note - This may not be called for every load, it is possible for example for loads to fail\n   * before the load starts (when the model object is null).\n   *\n   * <p>Note - This method may be called multiple times before any other lifecycle method is called.\n   * Loads can be paused and restarted due to lifecycle or connectivity events and each restart may\n   * cause a call here.\n   *\n   * @param placeholder The placeholder drawable to optionally show, or null.\n   */\n  void onLoadStarted(@Nullable Drawable placeholder);\n\n  /**\n   * A <b>mandatory</b> lifecycle callback that is called when a load fails.\n   *\n   * <p>Note - This may be called before {@link #onLoadStarted(android.graphics.drawable.Drawable) }\n   * if the model object is null.\n   *\n   * <p>You <b>must</b> ensure that any current Drawable received in {@link #onResourceReady(Object,\n   * Transition)} is no longer used before redrawing the container (usually a View) or changing its\n   * visibility.\n   *\n   * @param errorDrawable The error drawable to optionally show, or null.\n   */\n  void onLoadFailed(@Nullable Drawable errorDrawable);\n\n  /**\n   * The method that will be called when the resource load has finished.\n   *\n   * <p>This may be called multiple times both within a single load and also across different loads\n   * if the {@code Target} object is re-used.\n   *\n   * <p>Within a single load this may be called multiple times for reasons that include:\n   *\n   * <ul>\n   *   <li>The load uses one or more thumbnails. Each time a thumbnail load completes successfully\n   *       and no higher priority load has finished, this method will be called with the thumbnail\n   *       resource. See {@link\n   *       com.bumptech.glide.RequestBuilder#thumbnail(com.bumptech.glide.RequestBuilder)}.\n   *   <li>The load is paused and restarted. This can happen automatically in response to\n   *       connectivity changes or the Activity / Fragment lifecycle. It can also happen if {@link\n   *       com.bumptech.glide.RequestManager#pauseRequests()} is called manually.\n   * </ul>\n   *\n   * @param resource the loaded resource.\n   */\n  void onResourceReady(@NonNull R resource, @Nullable Transition<? super R> transition);\n\n  /**\n   * A <b>mandatory</b> lifecycle callback that is called when a load is cancelled and its resources\n   * are freed.\n   *\n   * <p>You <b>must</b> ensure that any current Drawable received in {@link #onResourceReady(Object,\n   * Transition)} is no longer used before redrawing the container (usually a View) or changing its\n   * visibility.\n   *\n   * @param placeholder The placeholder drawable to optionally show, or null.\n   */\n  void onLoadCleared(@Nullable Drawable placeholder);\n\n  /**\n   * A method to retrieve the size of this target.\n   *\n   * @param cb The callback that must be called when the size of the target has been determined\n   */\n  void getSize(@NonNull SizeReadyCallback cb);\n\n  /**\n   * Removes the given callback from the pending set if it's still retained.\n   *\n   * @param cb The callback to remove.\n   */\n  void removeCallback(@NonNull SizeReadyCallback cb);\n\n  /** Sets the current request for this target to retain, should not be called outside of Glide. */\n  void setRequest(@Nullable Request request);\n\n  /** Retrieves the current request for this target, should not be called outside of Glide. */\n  @Nullable\n  Request getRequest();\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/ThumbnailImageViewTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport androidx.annotation.Nullable;\n\n/**\n * Avoids extra calls to {@link android.view.View#requestLayout} when loading more than once image\n * into an {@link android.widget.ImageView} with fixed dimensions.\n *\n * <p>Typically it makes sense to use this class when loading multiple images with the {@link\n * com.bumptech.glide.RequestBuilder#thumbnail(com.bumptech.glide.RequestBuilder)} API into views in\n * a scrolling list like ListView, GridView, or RecyclerView.\n *\n * <p>{@link FixedSizeDrawable} may cause skewing or other undesirable behavior depending on your\n * images, views, and scaling. If this occurs, consider {@link DrawableImageViewTarget} or {@link\n * BitmapImageViewTarget} as alternatives.\n *\n * @param <T> The type of resource that will be displayed in the ImageView.\n */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic abstract class ThumbnailImageViewTarget<T> extends ImageViewTarget<T> {\n\n  public ThumbnailImageViewTarget(ImageView view) {\n    super(view);\n  }\n\n  /**\n   * @deprecated Use {@link #waitForLayout()} insetad.\n   */\n  @Deprecated\n  @SuppressWarnings({\"deprecation\"})\n  public ThumbnailImageViewTarget(ImageView view, boolean waitForLayout) {\n    super(view, waitForLayout);\n  }\n\n  @Override\n  protected void setResource(@Nullable T resource) {\n    ViewGroup.LayoutParams layoutParams = view.getLayoutParams();\n    Drawable result = getDrawable(resource);\n    if (layoutParams != null && layoutParams.width > 0 && layoutParams.height > 0) {\n      result = new FixedSizeDrawable(result, layoutParams.width, layoutParams.height);\n    }\n\n    view.setImageDrawable(result);\n  }\n\n  protected abstract Drawable getDrawable(T resource);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/target/ViewTarget.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport android.content.Context;\nimport android.graphics.Point;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport android.view.Display;\nimport android.view.View;\nimport android.view.View.OnAttachStateChangeListener;\nimport android.view.ViewGroup.LayoutParams;\nimport android.view.ViewTreeObserver;\nimport android.view.WindowManager;\nimport androidx.annotation.CallSuper;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.R;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.Synthetic;\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A base {@link Target} for loading {@link android.graphics.Bitmap}s into {@link View}s that\n * provides default implementations for most most methods and can determine the size of views using\n * a {@link android.view.ViewTreeObserver.OnDrawListener}.\n *\n * <p>To detect {@link View} reuse in {@link android.widget.ListView} or any {@link\n * android.view.ViewGroup} that reuses views, this class uses the {@link View#setTag(Object)} method\n * to store some metadata so that if a view is reused, any previous loads or resources from previous\n * loads can be cancelled or reused.\n *\n * <p>Any calls to {@link View#setTag(Object)}} on a View given to this class will result in\n * excessive allocations and and/or {@link IllegalArgumentException}s. If you must call {@link\n * View#setTag(Object)} on a view, use {@link #setTagId(int)} to specify a custom tag for Glide to\n * use.\n *\n * <p>Subclasses must call super in {@link #onLoadCleared(Drawable)}\n *\n * @param <T> The specific subclass of view wrapped by this target.\n * @param <Z> The resource type this target will receive.\n * @deprecated Use {@link CustomViewTarget}. Using this class is unsafe without implementing {@link\n *     #onLoadCleared} and results in recycled bitmaps being referenced from the UI and hard to\n *     root-cause crashes.\n */\n@Deprecated\npublic abstract class ViewTarget<T extends View, Z> extends BaseTarget<Z> {\n  private static final String TAG = \"ViewTarget\";\n  private static boolean isTagUsedAtLeastOnce;\n  private static int tagId = R.id.glide_custom_view_target_tag;\n\n  protected final T view;\n  private final SizeDeterminer sizeDeterminer;\n  @Nullable private OnAttachStateChangeListener attachStateListener;\n  private boolean isClearedByUs;\n  private boolean isAttachStateListenerAdded;\n\n  /** Constructor that defaults {@code waitForLayout} to {@code false}. */\n  public ViewTarget(@NonNull T view) {\n    this.view = Preconditions.checkNotNull(view);\n    sizeDeterminer = new SizeDeterminer(view);\n  }\n\n  /**\n   * @param waitForLayout If set to {@code true}, Glide will always wait for any pending layout pass\n   *     before checking for the size a View. If set to {@code false} Glide will only wait for a\n   *     pending layout pass if it's unable to resolve the size from layout parameters or an\n   *     existing View size. Because setting this parameter to {@code true} forces Glide to wait for\n   *     the layout pass to occur before starting the load, setting this parameter to {@code true}\n   *     can cause flashing in some cases and should be used sparingly. If layout parameters are set\n   *     to fixed sizes, they will still be used instead of the View's dimensions even if this\n   *     parameter is set to {@code true}. This parameter is a fallback only.\n   * @deprecated Use {@link #waitForLayout()} instead.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @Deprecated\n  public ViewTarget(@NonNull T view, boolean waitForLayout) {\n    this(view);\n    if (waitForLayout) {\n      waitForLayout();\n    }\n  }\n\n  /**\n   * Clears the {@link View}'s {@link Request} when the {@link View} is detached from its {@link\n   * android.view.Window} and restarts the {@link Request} when the {@link View} is re-attached from\n   * its {@link android.view.Window}.\n   *\n   * <p>This is an experimental API that may be removed in a future version.\n   *\n   * <p>Using this method can save memory by allowing Glide to more eagerly clear resources when\n   * transitioning screens or swapping adapters in scrolling views. However it also substantially\n   * increases the odds that images will not be in memory if users subsequently return to a screen\n   * where images were previously loaded. Whether or not this happens will depend on the number of\n   * images loaded in the new screen and the size of the memory cache. Increasing the size of the\n   * memory cache can improve this behavior but it largely negates the memory benefits of using this\n   * method.\n   *\n   * <p>Use this method with caution and measure your memory usage to ensure that it's actually\n   * improving your memory usage in the cases you care about.\n   */\n  // Public API.\n  @NonNull\n  @SuppressWarnings({\"UnusedReturnValue\", \"WeakerAccess\"})\n  public final ViewTarget<T, Z> clearOnDetach() {\n    if (attachStateListener != null) {\n      return this;\n    }\n    attachStateListener =\n        new OnAttachStateChangeListener() {\n          @Override\n          public void onViewAttachedToWindow(View v) {\n            resumeMyRequest();\n          }\n\n          @Override\n          public void onViewDetachedFromWindow(View v) {\n            pauseMyRequest();\n          }\n        };\n    maybeAddAttachStateListener();\n    return this;\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  void resumeMyRequest() {\n    Request request = getRequest();\n    if (request != null && request.isCleared()) {\n      request.begin();\n    }\n  }\n\n  @SuppressWarnings(\"WeakerAccess\")\n  @Synthetic\n  void pauseMyRequest() {\n    Request request = getRequest();\n    // If the Request were cleared by the developer, it would be null here. The only way it's\n    // present is if the developer hasn't previously cleared this Target.\n    if (request != null) {\n      isClearedByUs = true;\n      request.clear();\n      isClearedByUs = false;\n    }\n  }\n\n  /**\n   * Indicates that Glide should always wait for any pending layout pass before checking for the\n   * size an {@link View}.\n   *\n   * <p>By default, Glide will only wait for a pending layout pass if it's unable to resolve the\n   * size from the {@link LayoutParams} or valid non-zero values for {@link View#getWidth()} and\n   * {@link View#getHeight()}.\n   *\n   * <p>Because calling this method forces Glide to wait for the layout pass to occur before\n   * starting loads, setting this parameter to {@code true} can cause Glide to asynchronous load an\n   * image even if it's in the memory cache. The load will happen asynchronously because Glide has\n   * to wait for a layout pass to occur, which won't necessarily happen in the same frame as when\n   * the image is requested. As a result, using this method can resulting in flashing in some cases\n   * and should be used sparingly.\n   *\n   * <p>If the {@link LayoutParams} of the wrapped {@link View} are set to fixed sizes, they will\n   * still be used instead of the {@link View}'s dimensions even if this method is called. This\n   * parameter is a fallback only.\n   */\n  @SuppressWarnings(\"WeakerAccess\") // Public API\n  @NonNull\n  public final ViewTarget<T, Z> waitForLayout() {\n    sizeDeterminer.waitForLayout = true;\n    return this;\n  }\n\n  @CallSuper\n  @Override\n  public void onLoadStarted(@Nullable Drawable placeholder) {\n    super.onLoadStarted(placeholder);\n    maybeAddAttachStateListener();\n  }\n\n  private void maybeAddAttachStateListener() {\n    if (attachStateListener == null || isAttachStateListenerAdded) {\n      return;\n    }\n\n    view.addOnAttachStateChangeListener(attachStateListener);\n    isAttachStateListenerAdded = true;\n  }\n\n  private void maybeRemoveAttachStateListener() {\n    if (attachStateListener == null || !isAttachStateListenerAdded) {\n      return;\n    }\n\n    view.removeOnAttachStateChangeListener(attachStateListener);\n    isAttachStateListenerAdded = false;\n  }\n\n  /** Returns the wrapped {@link android.view.View}. */\n  @NonNull\n  public T getView() {\n    return view;\n  }\n\n  /**\n   * Determines the size of the view by first checking {@link android.view.View#getWidth()} and\n   * {@link android.view.View#getHeight()}. If one or both are zero, it then checks the view's\n   * {@link LayoutParams}. If one or both of the params width and height are less than or equal to\n   * zero, it then adds an {@link android.view.ViewTreeObserver.OnPreDrawListener} which waits until\n   * the view has been measured before calling the callback with the view's drawn width and height.\n   *\n   * @param cb {@inheritDoc}\n   */\n  @CallSuper\n  @Override\n  public void getSize(@NonNull SizeReadyCallback cb) {\n    sizeDeterminer.getSize(cb);\n  }\n\n  @CallSuper\n  @Override\n  public void removeCallback(@NonNull SizeReadyCallback cb) {\n    sizeDeterminer.removeCallback(cb);\n  }\n\n  @CallSuper\n  @Override\n  public void onLoadCleared(@Nullable Drawable placeholder) {\n    super.onLoadCleared(placeholder);\n    sizeDeterminer.clearCallbacksAndListener();\n\n    if (!isClearedByUs) {\n      maybeRemoveAttachStateListener();\n    }\n  }\n\n  /**\n   * Stores the request using {@link View#setTag(Object)}.\n   *\n   * @param request {@inheritDoc}\n   */\n  @Override\n  public void setRequest(@Nullable Request request) {\n    setTag(request);\n  }\n\n  /**\n   * Returns any stored request using {@link android.view.View#getTag()}.\n   *\n   * <p>For Glide to function correctly, Glide must be the only thing that calls {@link\n   * View#setTag(Object)}. If the tag is cleared or put to another object type, Glide will not be\n   * able to retrieve and cancel previous loads which will not only prevent Glide from reusing\n   * resource, but will also result in incorrect images being loaded and lots of flashing of images\n   * in lists. As a result, this will throw an {@link java.lang.IllegalArgumentException} if {@link\n   * android.view.View#getTag()}} returns a non null object that is not an {@link\n   * com.bumptech.glide.request.Request}.\n   */\n  @Override\n  @Nullable\n  public Request getRequest() {\n    Object tag = getTag();\n    Request request = null;\n    if (tag != null) {\n      if (tag instanceof Request) {\n        request = (Request) tag;\n      } else {\n        throw new IllegalArgumentException(\n            \"You must not call setTag() on a view Glide is targeting\");\n      }\n    }\n    return request;\n  }\n\n  @Override\n  public String toString() {\n    return \"Target for: \" + view;\n  }\n\n  private void setTag(@Nullable Object tag) {\n    isTagUsedAtLeastOnce = true;\n    view.setTag(tagId, tag);\n  }\n\n  @Nullable\n  private Object getTag() {\n    return view.getTag(tagId);\n  }\n\n  /**\n   * Sets the android resource id to use in conjunction with {@link View#setTag(int, Object)} to\n   * store temporary state allowing loads to be automatically cancelled and resources re-used in\n   * scrolling lists.\n   *\n   * <p>If no tag id is set, Glide will use {@link View#setTag(Object)}.\n   *\n   * <p>Warning: prior to Android 4.0 tags were stored in a static map. Using this method prior to\n   * Android 4.0 may cause memory leaks and isn't recommended. If you do use this method on older\n   * versions, be sure to call {@link com.bumptech.glide.RequestManager#clear(View)} on any view you\n   * start a load into to ensure that the static state is removed.\n   *\n   * @deprecated Glide uses it's own default tag id, so there's no need to specify your own. This\n   *     method will be removed in a future version.\n   * @param tagId The android resource to use.\n   */\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  @Deprecated\n  public static void setTagId(int tagId) {\n    if (isTagUsedAtLeastOnce) {\n      throw new IllegalArgumentException(\n          \"You cannot set the tag id more than once or change\"\n              + \" the tag id after the first request has been made\");\n    }\n    ViewTarget.tagId = tagId;\n  }\n\n  @VisibleForTesting\n  static final class SizeDeterminer {\n    // Some negative sizes (Target.SIZE_ORIGINAL) are valid, 0 is never valid.\n    private static final int PENDING_SIZE = 0;\n    @VisibleForTesting @Nullable static Integer maxDisplayLength;\n    private final View view;\n    private final List<SizeReadyCallback> cbs = new ArrayList<>();\n    @Synthetic boolean waitForLayout;\n\n    @Nullable private SizeDeterminerLayoutListener layoutListener;\n\n    SizeDeterminer(@NonNull View view) {\n      this.view = view;\n    }\n\n    // Use the maximum to avoid depending on the device's current orientation.\n    private static int getMaxDisplayLength(@NonNull Context context) {\n      if (maxDisplayLength == null) {\n        WindowManager windowManager =\n            (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n        Display display = Preconditions.checkNotNull(windowManager).getDefaultDisplay();\n        Point displayDimensions = new Point();\n        display.getSize(displayDimensions);\n        maxDisplayLength = Math.max(displayDimensions.x, displayDimensions.y);\n      }\n      return maxDisplayLength;\n    }\n\n    private void notifyCbs(int width, int height) {\n      // One or more callbacks may trigger the removal of one or more additional callbacks, so we\n      // need a copy of the list to avoid a concurrent modification exception. One place this\n      // happens is when a full request completes from the in memory cache while its thumbnail is\n      // still being loaded asynchronously. See #2237.\n      for (SizeReadyCallback cb : new ArrayList<>(cbs)) {\n        cb.onSizeReady(width, height);\n      }\n    }\n\n    @Synthetic\n    void checkCurrentDimens() {\n      if (cbs.isEmpty()) {\n        return;\n      }\n\n      int currentWidth = getTargetWidth();\n      int currentHeight = getTargetHeight();\n      if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {\n        return;\n      }\n\n      notifyCbs(currentWidth, currentHeight);\n      clearCallbacksAndListener();\n    }\n\n    void getSize(@NonNull SizeReadyCallback cb) {\n      int currentWidth = getTargetWidth();\n      int currentHeight = getTargetHeight();\n      if (isViewStateAndSizeValid(currentWidth, currentHeight)) {\n        cb.onSizeReady(currentWidth, currentHeight);\n        return;\n      }\n\n      // We want to notify callbacks in the order they were added and we only expect one or two\n      // callbacks to be added a time, so a List is a reasonable choice.\n      if (!cbs.contains(cb)) {\n        cbs.add(cb);\n      }\n      if (layoutListener == null) {\n        ViewTreeObserver observer = view.getViewTreeObserver();\n        layoutListener = new SizeDeterminerLayoutListener(this);\n        observer.addOnPreDrawListener(layoutListener);\n      }\n    }\n\n    /**\n     * The callback may be called anyway if it is removed by another {@link SizeReadyCallback} or\n     * otherwise removed while we're notifying the list of callbacks.\n     *\n     * <p>See #2237.\n     */\n    void removeCallback(@NonNull SizeReadyCallback cb) {\n      cbs.remove(cb);\n    }\n\n    void clearCallbacksAndListener() {\n      // Keep a reference to the layout attachStateListener and remove it here\n      // rather than having the observer remove itself because the observer\n      // we add the attachStateListener to will be almost immediately merged into\n      // another observer and will therefore never be alive. If we instead\n      // keep a reference to the attachStateListener and remove it here, we get the\n      // current view tree observer and should succeed.\n      ViewTreeObserver observer = view.getViewTreeObserver();\n      if (observer.isAlive()) {\n        observer.removeOnPreDrawListener(layoutListener);\n      }\n      layoutListener = null;\n      cbs.clear();\n    }\n\n    private boolean isViewStateAndSizeValid(int width, int height) {\n      return isDimensionValid(width) && isDimensionValid(height);\n    }\n\n    private int getTargetHeight() {\n      int verticalPadding = view.getPaddingTop() + view.getPaddingBottom();\n      LayoutParams layoutParams = view.getLayoutParams();\n      int layoutParamSize = layoutParams != null ? layoutParams.height : PENDING_SIZE;\n      return getTargetDimen(view.getHeight(), layoutParamSize, verticalPadding);\n    }\n\n    private int getTargetWidth() {\n      int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();\n      LayoutParams layoutParams = view.getLayoutParams();\n      int layoutParamSize = layoutParams != null ? layoutParams.width : PENDING_SIZE;\n      return getTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding);\n    }\n\n    private int getTargetDimen(int viewSize, int paramSize, int paddingSize) {\n      // We consider the View state as valid if the View has non-null layout params and a non-zero\n      // layout params width and height. This is imperfect. We're making an assumption that View\n      // parents will obey their child's layout parameters, which isn't always the case.\n      int adjustedParamSize = paramSize - paddingSize;\n      if (adjustedParamSize > 0) {\n        return adjustedParamSize;\n      }\n\n      // Since we always prefer layout parameters with fixed sizes, even if waitForLayout is true,\n      // we might as well ignore it and just return the layout parameters above if we have them.\n      // Otherwise we should wait for a layout pass before checking the View's dimensions.\n      if (waitForLayout && view.isLayoutRequested()) {\n        return PENDING_SIZE;\n      }\n\n      // We also consider the View state valid if the View has a non-zero width and height. This\n      // means that the View has gone through at least one layout pass. It does not mean the Views\n      // width and height are from the current layout pass. For example, if a View is re-used in\n      // RecyclerView or ListView, this width/height may be from an old position. In some cases\n      // the dimensions of the View at the old position may be different than the dimensions of the\n      // View in the new position because the LayoutManager/ViewParent can arbitrarily decide to\n      // change them. Nevertheless, in most cases this should be a reasonable choice.\n      int adjustedViewSize = viewSize - paddingSize;\n      if (adjustedViewSize > 0) {\n        return adjustedViewSize;\n      }\n\n      // Finally we consider the view valid if the layout parameter size is set to wrap_content.\n      // It's difficult for Glide to figure out what to do here. Although Target.SIZE_ORIGINAL is a\n      // coherent choice, it's extremely dangerous because original images may be much too large to\n      // fit in memory or so large that only a couple can fit in memory, causing OOMs. If users want\n      // the original image, they can always use .override(Target.SIZE_ORIGINAL). Since wrap_content\n      // may never resolve to a real size unless we load something, we aim for a square whose length\n      // is the largest screen size. That way we're loading something and that something has some\n      // hope of being downsampled to a size that the device can support. We also log a warning that\n      // tries to explain what Glide is doing and why some alternatives are preferable.\n      // Since WRAP_CONTENT is sometimes used as a default layout parameter, we always wait for\n      // layout to complete before using this fallback parameter (ConstraintLayout among others).\n      if (!view.isLayoutRequested() && paramSize == LayoutParams.WRAP_CONTENT) {\n        if (Log.isLoggable(TAG, Log.INFO)) {\n          Log.i(\n              TAG,\n              \"Glide treats LayoutParams.WRAP_CONTENT as a request for an image the size of this\"\n                  + \" device's screen dimensions. If you want to load the original image and are\"\n                  + \" ok with the corresponding memory cost and OOMs (depending on the input size),\"\n                  + \" use override(Target.SIZE_ORIGINAL). Otherwise, use LayoutParams.MATCH_PARENT,\"\n                  + \" set layout_width and layout_height to fixed dimension, or use .override()\"\n                  + \" with fixed dimensions.\");\n        }\n        return getMaxDisplayLength(view.getContext());\n      }\n\n      // If the layout parameters are < padding, the view size is < padding, or the layout\n      // parameters are set to match_parent or wrap_content and no layout has occurred, we should\n      // wait for layout and repeat.\n      return PENDING_SIZE;\n    }\n\n    private boolean isDimensionValid(int size) {\n      return size > 0 || size == SIZE_ORIGINAL;\n    }\n\n    private static final class SizeDeterminerLayoutListener\n        implements ViewTreeObserver.OnPreDrawListener {\n      private final WeakReference<SizeDeterminer> sizeDeterminerRef;\n\n      SizeDeterminerLayoutListener(@NonNull SizeDeterminer sizeDeterminer) {\n        sizeDeterminerRef = new WeakReference<>(sizeDeterminer);\n      }\n\n      @Override\n      public boolean onPreDraw() {\n        if (Log.isLoggable(TAG, Log.VERBOSE)) {\n          Log.v(TAG, \"OnGlobalLayoutListener called attachStateListener=\" + this);\n        }\n        SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();\n        if (sizeDeterminer != null) {\n          sizeDeterminer.checkCurrentDimens();\n        }\n        return true;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/BitmapContainerTransitionFactory.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport com.bumptech.glide.load.DataSource;\n\n/**\n * A {@link TransitionFactory} for complex types that have a {@link android.graphics.Bitmap} inside.\n * The transitioning bitmap is wrapped in a {@link android.graphics.drawable.BitmapDrawable}. Most\n * commonly used with {@link DrawableCrossFadeFactory}.\n *\n * @param <R> The type of the composite object that contains the {@link android.graphics.Bitmap} to\n *     be transitioned.\n */\npublic abstract class BitmapContainerTransitionFactory<R> implements TransitionFactory<R> {\n  private final TransitionFactory<Drawable> realFactory;\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public BitmapContainerTransitionFactory(TransitionFactory<Drawable> realFactory) {\n    this.realFactory = realFactory;\n  }\n\n  @Override\n  public Transition<R> build(DataSource dataSource, boolean isFirstResource) {\n    Transition<Drawable> transition = realFactory.build(dataSource, isFirstResource);\n    return new BitmapGlideAnimation(transition);\n  }\n\n  /**\n   * Retrieve the Bitmap from a composite object.\n   *\n   * <p><b>Warning:</b> Do not convert any arbitrary object to Bitmap via expensive drawing here,\n   * this method is called on the UI thread.\n   *\n   * @param current composite object containing a Bitmap and some other information\n   * @return the Bitmap contained within {@code current}\n   */\n  protected abstract Bitmap getBitmap(R current);\n\n  private final class BitmapGlideAnimation implements Transition<R> {\n    private final Transition<Drawable> transition;\n\n    BitmapGlideAnimation(Transition<Drawable> transition) {\n      this.transition = transition;\n    }\n\n    @Override\n    public boolean transition(R current, ViewAdapter adapter) {\n      Resources resources = adapter.getView().getResources();\n      Drawable currentBitmap = new BitmapDrawable(resources, getBitmap(current));\n      return transition.transition(currentBitmap, adapter);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/BitmapTransitionFactory.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\n\n/**\n * A {@link TransitionFactory} for {@link android.graphics.Bitmap}s that uses a Drawable transition\n * factory to transition from an existing drawable already visible on the target to the new bitmap.\n *\n * @see BitmapContainerTransitionFactory\n */\npublic class BitmapTransitionFactory extends BitmapContainerTransitionFactory<Bitmap> {\n  public BitmapTransitionFactory(@NonNull TransitionFactory<Drawable> realFactory) {\n    super(realFactory);\n  }\n\n  @Override\n  @NonNull\n  protected Bitmap getBitmap(@NonNull Bitmap current) {\n    return current;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/DrawableCrossFadeFactory.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport android.graphics.drawable.Drawable;\nimport com.bumptech.glide.load.DataSource;\n\n/**\n * A factory class that produces a new {@link Transition} that varies depending on whether or not\n * the drawable was loaded from the memory cache and whether or not the drawable is the first image\n * to be put on the target.\n *\n * <p>Resources are usually loaded from the memory cache just before the user can see the view, for\n * example when the user changes screens or scrolls back and forth in a list. In those cases the\n * user typically does not expect to see a transition. As a result, when the resource is loaded from\n * the memory cache this factory produces an {@link NoTransition}.\n */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic class DrawableCrossFadeFactory implements TransitionFactory<Drawable> {\n  private final int duration;\n  private final boolean isCrossFadeEnabled;\n  private DrawableCrossFadeTransition resourceTransition;\n\n  protected DrawableCrossFadeFactory(int duration, boolean isCrossFadeEnabled) {\n    this.duration = duration;\n    this.isCrossFadeEnabled = isCrossFadeEnabled;\n  }\n\n  @Override\n  public Transition<Drawable> build(DataSource dataSource, boolean isFirstResource) {\n    return dataSource == DataSource.MEMORY_CACHE\n        ? NoTransition.<Drawable>get()\n        : getResourceTransition();\n  }\n\n  private Transition<Drawable> getResourceTransition() {\n    if (resourceTransition == null) {\n      resourceTransition = new DrawableCrossFadeTransition(duration, isCrossFadeEnabled);\n    }\n    return resourceTransition;\n  }\n\n  /** A Builder for {@link DrawableCrossFadeFactory}. */\n  @SuppressWarnings(\"unused\")\n  public static class Builder {\n    private static final int DEFAULT_DURATION_MS = 300;\n    private final int durationMillis;\n    private boolean isCrossFadeEnabled;\n\n    public Builder() {\n      this(DEFAULT_DURATION_MS);\n    }\n\n    /**\n     * @param durationMillis The duration of the cross fade animation in milliseconds.\n     */\n    public Builder(int durationMillis) {\n      this.durationMillis = durationMillis;\n    }\n\n    /**\n     * Enables or disables animating the alpha of the {@link Drawable} the cross fade will animate\n     * from.\n     *\n     * <p>Defaults to {@code false}.\n     *\n     * @param isCrossFadeEnabled If {@code true} the previous {@link Drawable}'s alpha will be\n     *     animated from 100 to 0 while the new {@link Drawable}'s alpha is animated from 0 to 100.\n     *     Otherwise the previous {@link Drawable}'s alpha will remain at 100 throughout the\n     *     animation. See {@link\n     *     android.graphics.drawable.TransitionDrawable#setCrossFadeEnabled(boolean)}\n     */\n    public Builder setCrossFadeEnabled(boolean isCrossFadeEnabled) {\n      this.isCrossFadeEnabled = isCrossFadeEnabled;\n      return this;\n    }\n\n    public DrawableCrossFadeFactory build() {\n      return new DrawableCrossFadeFactory(durationMillis, isCrossFadeEnabled);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/DrawableCrossFadeTransition.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.TransitionDrawable;\n\n/**\n * A cross fade {@link Transition} for {@link android.graphics.drawable.Drawable}s that uses an\n * {@link android.graphics.drawable.TransitionDrawable} to transition from an existing drawable\n * already visible on the target to a new drawable. If no existing drawable exists, this class can\n * instead fall back to a default animation that doesn't rely on {@link\n * android.graphics.drawable.TransitionDrawable}.\n */\npublic class DrawableCrossFadeTransition implements Transition<Drawable> {\n  private final int duration;\n  private final boolean isCrossFadeEnabled;\n\n  /**\n   * @param duration The duration that the cross fade animation should run if there is something to\n   *     cross fade from when a new {@link android.graphics.drawable.Drawable} is put.\n   * @param isCrossFadeEnabled If {@code true}, animates the previous resource's alpha to 0 while\n   *     animating the new resource's alpha to 100. Otherwise, only animates the new resource's\n   *     alpha to 100 while leaving the previous resource's alpha at 100. See {@link\n   *     TransitionDrawable#setCrossFadeEnabled(boolean)}.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public DrawableCrossFadeTransition(int duration, boolean isCrossFadeEnabled) {\n    this.duration = duration;\n    this.isCrossFadeEnabled = isCrossFadeEnabled;\n  }\n\n  /**\n   * Animates from the previous drawable to the current drawable in one of two ways.\n   *\n   * <ol>\n   *   <li>Using the default animation provided in the constructor if the previous drawable is null\n   *   <li>Using the cross fade animation with the duration provided in the constructor if the\n   *       previous drawable is non null\n   * </ol>\n   *\n   * @param current {@inheritDoc}\n   * @param adapter {@inheritDoc}\n   * @return {@inheritDoc}\n   */\n  @Override\n  public boolean transition(Drawable current, ViewAdapter adapter) {\n    Drawable previous = adapter.getCurrentDrawable();\n    if (previous == null) {\n      previous = new ColorDrawable(Color.TRANSPARENT);\n    }\n    TransitionDrawable transitionDrawable =\n        new TransitionDrawable(new Drawable[] {previous, current});\n    transitionDrawable.setCrossFadeEnabled(isCrossFadeEnabled);\n    transitionDrawable.startTransition(duration);\n    adapter.setDrawable(transitionDrawable);\n    return true;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/NoTransition.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.util.Synthetic;\n\n/**\n * A simple {@link Transition} that performs no actions.\n *\n * @param <R> the resource type that will be transitioned into a {@link\n *     com.bumptech.glide.request.target.Target}.\n */\npublic class NoTransition<R> implements Transition<R> {\n  @Synthetic static final NoTransition<?> NO_ANIMATION = new NoTransition<>();\n\n  @SuppressWarnings(\"rawtypes\")\n  private static final TransitionFactory<?> NO_ANIMATION_FACTORY = new NoAnimationFactory();\n\n  /**\n   * A factory that always returns the same {@link NoTransition}.\n   *\n   * @param <R> the resource type that will be transitioned into a {@link\n   *     com.bumptech.glide.request.target.Target}.\n   */\n  public static class NoAnimationFactory<R> implements TransitionFactory<R> {\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public Transition<R> build(DataSource dataSource, boolean isFirstResource) {\n      return (Transition<R>) NO_ANIMATION;\n    }\n  }\n\n  /** Returns an instance of a factory that produces {@link NoTransition}s. */\n  @SuppressWarnings(\"unchecked\")\n  public static <R> TransitionFactory<R> getFactory() {\n    return (TransitionFactory<R>) NO_ANIMATION_FACTORY;\n  }\n\n  /** Returns an instance of {@link NoTransition}. */\n  @SuppressWarnings(\"unchecked\")\n  public static <R> Transition<R> get() {\n    return (Transition<R>) NO_ANIMATION;\n  }\n\n  /** Performs no animation and always returns {@code false}. */\n  @Override\n  public boolean transition(Object current, ViewAdapter adapter) {\n    return false;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/Transition.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport androidx.annotation.Nullable;\n\n/**\n * An interface that allows a transition to be applied to {@link android.view.View}s in {@link\n * com.bumptech.glide.request.target.Target}s in across resource types. Targets that wrap views will\n * be able to provide all of the necessary arguments and start the transition. Those that do not\n * will be unable to provide the necessary arguments and will therefore be forced to ignore the\n * transition. This interface is a compromise that allows view specific transition in Glide's\n * complex world of arbitrary resource types and arbitrary target types.\n *\n * @param <R> The type of the resource whose entrance will be transitioned.\n */\npublic interface Transition<R> {\n\n  /**\n   * An interface wrapping a view that exposes the necessary methods to run the various types of\n   * android animations as transitions: ({@link ViewTransition}, {@link ViewPropertyTransition} and\n   * animated {@link android.graphics.drawable.Drawable}s).\n   */\n  interface ViewAdapter {\n    /** Returns the wrapped {@link android.view.View}. */\n    View getView();\n\n    /**\n     * Returns the current drawable being displayed in the view, or null if no such drawable exists\n     * (or one cannot be retrieved).\n     */\n    @Nullable\n    Drawable getCurrentDrawable();\n\n    /**\n     * Sets the current drawable (usually an animated drawable) to display in the wrapped view.\n     *\n     * @param drawable The drawable to display in the wrapped view.\n     */\n    void setDrawable(Drawable drawable);\n  }\n\n  /**\n   * Animates from the previous {@link android.graphics.drawable.Drawable} that is currently being\n   * displayed in the given view, if not null, to the new resource that should be displayed in the\n   * view.\n   *\n   * @param current The new resource that will be displayed in the view.\n   * @param adapter The {@link Transition.ViewAdapter} wrapping a view that can at least return an\n   *     {@link android.view.View} from {@link Transition.ViewAdapter#getView()}.\n   * @return True if in the process of running the transition, the new resource was put on the view,\n   *     false if the caller needs to manually put the current resource on the view.\n   */\n  boolean transition(R current, ViewAdapter adapter);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/TransitionFactory.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport com.bumptech.glide.load.DataSource;\n\n/**\n * A factory class that can produce different {@link Transition}s based on the state of the request.\n *\n * @param <R> The type of resource that needs to be animated into the target.\n */\npublic interface TransitionFactory<R> {\n\n  /**\n   * Returns a new {@link Transition}.\n   *\n   * @param dataSource The {@link com.bumptech.glide.load.DataSource} the resource was loaded from.\n   * @param isFirstResource True if this is the first resource to be loaded into the target.\n   */\n  Transition<R> build(DataSource dataSource, boolean isFirstResource);\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/ViewAnimationFactory.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport android.content.Context;\nimport android.view.animation.Animation;\nimport android.view.animation.AnimationUtils;\nimport com.bumptech.glide.load.DataSource;\n\n/**\n * A {@link TransitionFactory} that produces {@link ViewTransition}s.\n *\n * @param <R> The type of the resource that will be transitioned into a view.\n */\npublic class ViewAnimationFactory<R> implements TransitionFactory<R> {\n  private final ViewTransition.ViewTransitionAnimationFactory viewTransitionAnimationFactory;\n  private Transition<R> transition;\n\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public ViewAnimationFactory(Animation animation) {\n    this(new ConcreteViewTransitionAnimationFactory(animation));\n  }\n\n  public ViewAnimationFactory(int animationId) {\n    this(new ResourceViewTransitionAnimationFactory(animationId));\n  }\n\n  ViewAnimationFactory(\n      ViewTransition.ViewTransitionAnimationFactory viewTransitionAnimationFactory) {\n    this.viewTransitionAnimationFactory = viewTransitionAnimationFactory;\n  }\n\n  /**\n   * Returns a new {@link Transition} for the given arguments. If isFromMemoryCache is {@code true}\n   * or isFirstImage is {@code false}, returns a {@link NoTransition} and otherwise returns a new\n   * {@link ViewTransition}.\n   *\n   * @param dataSource {@inheritDoc}\n   * @param isFirstResource {@inheritDoc}\n   */\n  @Override\n  public Transition<R> build(DataSource dataSource, boolean isFirstResource) {\n    if (dataSource == DataSource.MEMORY_CACHE || !isFirstResource) {\n      return NoTransition.get();\n    }\n\n    if (transition == null) {\n      transition = new ViewTransition<>(viewTransitionAnimationFactory);\n    }\n\n    return transition;\n  }\n\n  private static class ConcreteViewTransitionAnimationFactory\n      implements ViewTransition.ViewTransitionAnimationFactory {\n    private final Animation animation;\n\n    ConcreteViewTransitionAnimationFactory(Animation animation) {\n      this.animation = animation;\n    }\n\n    @Override\n    public Animation build(Context context) {\n      return animation;\n    }\n  }\n\n  private static class ResourceViewTransitionAnimationFactory\n      implements ViewTransition.ViewTransitionAnimationFactory {\n    private final int animationId;\n\n    ResourceViewTransitionAnimationFactory(int animationId) {\n      this.animationId = animationId;\n    }\n\n    @Override\n    public Animation build(Context context) {\n      return AnimationUtils.loadAnimation(context, animationId);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/ViewPropertyAnimationFactory.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport com.bumptech.glide.load.DataSource;\n\n/**\n * A {@link TransitionFactory} that produces ViewPropertyAnimations.\n *\n * @param <R> The type of the resource that will be transitioned into a view.\n */\npublic class ViewPropertyAnimationFactory<R> implements TransitionFactory<R> {\n  private final ViewPropertyTransition.Animator animator;\n  private ViewPropertyTransition<R> animation;\n\n  public ViewPropertyAnimationFactory(ViewPropertyTransition.Animator animator) {\n    this.animator = animator;\n  }\n\n  /**\n   * Returns a new {@link Transition} for the given arguments. If isMemoryCache is {@code true} or\n   * isFirstImage is {@code false}, returns a {@link NoTransition} and otherwise returns a new\n   * {@link ViewPropertyTransition} for the {@link ViewPropertyTransition.Animator} provided in the\n   * constructor.\n   */\n  @Override\n  public Transition<R> build(DataSource dataSource, boolean isFirstResource) {\n    if (dataSource == DataSource.MEMORY_CACHE || !isFirstResource) {\n      return NoTransition.get();\n    }\n    if (animation == null) {\n      animation = new ViewPropertyTransition<>(animator);\n    }\n\n    return animation;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/ViewPropertyTransition.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport android.view.View;\n\n/**\n * A {@link Transition} that accepts an interface that can apply an animation like a {@link\n * android.view.ViewPropertyAnimator} or a {@link android.animation.ObjectAnimator} that can be used\n * to transition a resource into a {@link View}.\n *\n * @param <R> The type of the resource that will be transitioned into a view.\n */\npublic class ViewPropertyTransition<R> implements Transition<R> {\n\n  private final Animator animator;\n\n  /**\n   * Constructor for a view property animation that takes an {@link ViewPropertyTransition.Animator}\n   * interface that can apply a transition to a view.\n   *\n   * @param animator The animator to use.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public ViewPropertyTransition(Animator animator) {\n    this.animator = animator;\n  }\n\n  /**\n   * Always applies the {@link ViewPropertyTransition.Animator} given in the constructor to the\n   * given view and returns {@code false} because the animator cannot put the new resource on the\n   * view.\n   *\n   * @param current {@inheritDoc}\n   * @param adapter {@inheritDoc}\n   * @return {@inheritDoc}\n   */\n  @Override\n  public boolean transition(R current, ViewAdapter adapter) {\n    final View view = adapter.getView();\n    if (view != null) {\n      animator.animate(adapter.getView());\n    }\n    return false;\n  }\n\n  /**\n   * An interface that allows an animation to be applied on or started from an {@link\n   * android.view.View}.\n   */\n  public interface Animator {\n    /**\n     * Starts an animation on the given {@link android.view.View}.\n     *\n     * @param view The view to transition.\n     */\n    void animate(View view);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/request/transition/ViewTransition.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.animation.Animation;\n\n/**\n * A {@link Transition} that can apply a {@link android.view.animation.Animation Animation} to a\n * {@link android.view.View View} using {@link\n * android.view.View#startAnimation(android.view.animation.Animation)}.\n *\n * @param <R> The type of the resource that will be transitioned into a view.\n */\npublic class ViewTransition<R> implements Transition<R> {\n\n  private final ViewTransitionAnimationFactory viewTransitionAnimationFactory;\n\n  /**\n   * Constructs a new ViewAnimation that will start the given {@link android.view.animation\n   * .Animation}.\n   */\n  ViewTransition(ViewTransitionAnimationFactory viewTransitionAnimationFactory) {\n    this.viewTransitionAnimationFactory = viewTransitionAnimationFactory;\n  }\n\n  /**\n   * Always clears the current animation on the view using {@link\n   * android.view.View#clearAnimation()}, then starts the {@link android.view.animation.Animation}\n   * given in the constructor using {@link\n   * android.view.View#startAnimation(android.view.animation.Animation)} and then returns {@code\n   * false} because the animation does not actually put the current resource on the view.\n   *\n   * @param current {@inheritDoc}\n   * @param adapter {@inheritDoc}\n   * @return {@inheritDoc}\n   */\n  @Override\n  public boolean transition(R current, ViewAdapter adapter) {\n    View view = adapter.getView();\n    if (view != null) {\n      view.clearAnimation();\n      Animation animation = viewTransitionAnimationFactory.build(view.getContext());\n      view.startAnimation(animation);\n    }\n\n    return false;\n  }\n\n  interface ViewTransitionAnimationFactory {\n    Animation build(Context context);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/signature/AndroidResourceSignature.java",
    "content": "package com.bumptech.glide.signature;\n\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.util.Util;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\n\n/** Includes information about the package as well as whether or not the device is in night mode. */\npublic final class AndroidResourceSignature implements Key {\n\n  private final int nightMode;\n  private final Key applicationVersion;\n\n  @NonNull\n  public static Key obtain(@NonNull Context context) {\n    Key signature = ApplicationVersionSignature.obtain(context);\n    int nightMode =\n        context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;\n    return new AndroidResourceSignature(nightMode, signature);\n  }\n\n  private AndroidResourceSignature(int nightMode, Key applicationVersion) {\n    this.nightMode = nightMode;\n    this.applicationVersion = applicationVersion;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof AndroidResourceSignature) {\n      AndroidResourceSignature that = (AndroidResourceSignature) o;\n      return nightMode == that.nightMode && applicationVersion.equals(that.applicationVersion);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return Util.hashCode(applicationVersion, nightMode);\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    applicationVersion.updateDiskCacheKey(messageDigest);\n    byte[] nightModeData = ByteBuffer.allocate(4).putInt(nightMode).array();\n    messageDigest.update(nightModeData);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/signature/ApplicationVersionSignature.java",
    "content": "package com.bumptech.glide.signature;\n\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.VisibleForTesting;\nimport com.bumptech.glide.load.Key;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\n/**\n * A utility class for obtaining a {@link com.bumptech.glide.load.Key} signature containing the\n * application version name using {@link android.content.pm.PackageInfo#versionCode}.\n */\npublic final class ApplicationVersionSignature {\n  private static final String TAG = \"AppVersionSignature\";\n  private static final ConcurrentMap<String, Key> PACKAGE_NAME_TO_KEY = new ConcurrentHashMap<>();\n\n  /**\n   * Returns the signature {@link com.bumptech.glide.load.Key} for version code of the Application\n   * of the given Context.\n   */\n  @NonNull\n  public static Key obtain(@NonNull Context context) {\n    String packageName = context.getPackageName();\n    Key result = PACKAGE_NAME_TO_KEY.get(packageName);\n    if (result == null) {\n      Key toAdd = obtainVersionSignature(context);\n      result = PACKAGE_NAME_TO_KEY.putIfAbsent(packageName, toAdd);\n      // There wasn't a previous mapping, so toAdd is now the Key.\n      if (result == null) {\n        result = toAdd;\n      }\n    }\n\n    return result;\n  }\n\n  @VisibleForTesting\n  static void reset() {\n    PACKAGE_NAME_TO_KEY.clear();\n  }\n\n  @NonNull\n  private static Key obtainVersionSignature(@NonNull Context context) {\n    PackageInfo packageInfo = getPackageInfo(context);\n    String versionCode = getVersionCode(packageInfo);\n    return new ObjectKey(versionCode);\n  }\n\n  @NonNull\n  private static String getVersionCode(@Nullable PackageInfo packageInfo) {\n    String versionCode;\n    if (packageInfo != null) {\n      versionCode = String.valueOf(packageInfo.versionCode);\n    } else {\n      versionCode = UUID.randomUUID().toString();\n    }\n    return versionCode;\n  }\n\n  @Nullable\n  private static PackageInfo getPackageInfo(@NonNull Context context) {\n    try {\n      return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);\n    } catch (PackageManager.NameNotFoundException e) {\n      Log.e(TAG, \"Cannot resolve info for\" + context.getPackageName(), e);\n      return null;\n    }\n  }\n\n  private ApplicationVersionSignature() {\n    // Empty for visibility.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/signature/EmptySignature.java",
    "content": "package com.bumptech.glide.signature;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport java.security.MessageDigest;\n\n/** An empty key that is always equal to all other empty keys. */\npublic final class EmptySignature implements Key {\n  private static final EmptySignature EMPTY_KEY = new EmptySignature();\n\n  @NonNull\n  public static EmptySignature obtain() {\n    return EMPTY_KEY;\n  }\n\n  private EmptySignature() {\n    // Empty.\n  }\n\n  @Override\n  public String toString() {\n    return \"EmptySignature\";\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    // Do nothing.\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/signature/MediaStoreSignature.java",
    "content": "package com.bumptech.glide.signature;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Key;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\n\n/**\n * A unique signature based on metadata data from the media store that detects common changes to\n * media store files like edits, rotations, and temporary file replacement.\n */\npublic class MediaStoreSignature implements Key {\n  @NonNull private final String mimeType;\n  private final long dateModified;\n  private final int orientation;\n\n  /**\n   * Constructor for {@link com.bumptech.glide.signature.MediaStoreSignature}.\n   *\n   * @param mimeType The mime type of the media store media. Ok to default to empty string \"\". See\n   *     {@link android.provider.MediaStore.Images.ImageColumns#MIME_TYPE} or {@link\n   *     android.provider.MediaStore.Video.VideoColumns#MIME_TYPE}.\n   * @param dateModified The date modified time of the media store media. Ok to default to 0. See\n   *     {@link android.provider.MediaStore.Images.ImageColumns#DATE_MODIFIED} or {@link\n   *     android.provider.MediaStore.Video.VideoColumns#DATE_MODIFIED}.\n   * @param orientation The orientation of the media store media. Ok to default to 0. See {@link\n   *     android.provider.MediaStore.Images.ImageColumns#ORIENTATION}.\n   */\n  public MediaStoreSignature(@Nullable String mimeType, long dateModified, int orientation) {\n    this.mimeType = mimeType == null ? \"\" : mimeType;\n    this.dateModified = dateModified;\n    this.orientation = orientation;\n  }\n\n  @SuppressWarnings({\"PMD.SimplifyBooleanReturns\", \"RedundantIfStatement\"})\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    MediaStoreSignature that = (MediaStoreSignature) o;\n\n    if (dateModified != that.dateModified) {\n      return false;\n    }\n    if (orientation != that.orientation) {\n      return false;\n    }\n    if (!mimeType.equals(that.mimeType)) {\n      return false;\n    }\n    return true;\n  }\n\n  @Override\n  public int hashCode() {\n    int result = mimeType.hashCode();\n    result = 31 * result + (int) (dateModified ^ (dateModified >>> 32));\n    result = 31 * result + orientation;\n    return result;\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    byte[] data = ByteBuffer.allocate(12).putLong(dateModified).putInt(orientation).array();\n    messageDigest.update(data);\n    messageDigest.update(mimeType.getBytes(CHARSET));\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/signature/ObjectKey.java",
    "content": "package com.bumptech.glide.signature;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.util.Preconditions;\nimport java.security.MessageDigest;\n\n/**\n * Wraps an {@link java.lang.Object}, delegating {@link #equals(Object)} and {@link #hashCode()} to\n * the wrapped Object and providing the bytes of the result of the Object's {@link #toString()}\n * method to the {@link java.security.MessageDigest} in {@link\n * #updateDiskCacheKey(java.security.MessageDigest)}.\n *\n * <p>The Object's {@link #toString()} method must be unique and suitable for use as a disk cache\n * key.\n */\npublic final class ObjectKey implements Key {\n  private final Object object;\n\n  public ObjectKey(@NonNull Object object) {\n    this.object = Preconditions.checkNotNull(object);\n  }\n\n  @Override\n  public String toString() {\n    return \"ObjectKey{\" + \"object=\" + object + '}';\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof ObjectKey) {\n      ObjectKey other = (ObjectKey) o;\n      return object.equals(other.object);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return object.hashCode();\n  }\n\n  @Override\n  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n    messageDigest.update(object.toString().getBytes(CHARSET));\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/ByteBufferUtil.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.RandomAccessFile;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/** Utilities for interacting with {@link java.nio.ByteBuffer}s. */\n@SuppressWarnings({\"unused\", \"WeakerAccess\"}) // Public API\npublic final class ByteBufferUtil {\n  // 16 Kb\n  private static final int BUFFER_SIZE = 16384;\n  private static final AtomicReference<byte[]> BUFFER_REF = new AtomicReference<>();\n\n  private ByteBufferUtil() {\n    // Utility class.\n  }\n\n  @NonNull\n  public static ByteBuffer fromFile(@NonNull File file) throws IOException {\n    RandomAccessFile raf = null;\n    FileChannel channel = null;\n    try {\n      long fileLength = file.length();\n      // See #2240.\n      if (fileLength > Integer.MAX_VALUE) {\n        throw new IOException(\"File too large to map into memory\");\n      }\n      // See b/67710449.\n      if (fileLength == 0) {\n        throw new IOException(\"File unsuitable for memory mapping\");\n      }\n\n      raf = new RandomAccessFile(file, \"r\");\n      channel = raf.getChannel();\n      return channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength).load();\n    } finally {\n      if (channel != null) {\n        try {\n          channel.close();\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n      if (raf != null) {\n        try {\n          raf.close();\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n    }\n  }\n\n  public static void toFile(@NonNull ByteBuffer buffer, @NonNull File file) throws IOException {\n    rewind(buffer);\n    RandomAccessFile raf = null;\n    FileChannel channel = null;\n    try {\n      raf = new RandomAccessFile(file, \"rw\");\n      channel = raf.getChannel();\n      channel.write(buffer);\n      channel.force(false /*metadata*/);\n      channel.close();\n      raf.close();\n    } finally {\n      if (channel != null) {\n        try {\n          channel.close();\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n      if (raf != null) {\n        try {\n          raf.close();\n        } catch (IOException e) {\n          // Ignored.\n        }\n      }\n    }\n  }\n\n  public static void toStream(@NonNull ByteBuffer byteBuffer, @NonNull OutputStream os)\n      throws IOException {\n    SafeArray safeArray = getSafeArray(byteBuffer);\n    if (safeArray != null) {\n      os.write(safeArray.data, safeArray.offset, safeArray.offset + safeArray.limit);\n    } else {\n      byte[] buffer = BUFFER_REF.getAndSet(null);\n      if (buffer == null) {\n        buffer = new byte[BUFFER_SIZE];\n      }\n\n      while (byteBuffer.remaining() > 0) {\n        int toRead = Math.min(byteBuffer.remaining(), buffer.length);\n        byteBuffer.get(buffer, 0 /*dstOffset*/, toRead /*byteCount*/);\n        os.write(buffer, 0, toRead);\n      }\n\n      BUFFER_REF.set(buffer);\n    }\n  }\n\n  // We check the appropriate offsets, so this is a spurious warning.\n  @SuppressWarnings(\"ByteBufferBackingArray\")\n  @NonNull\n  public static byte[] toBytes(@NonNull ByteBuffer byteBuffer) {\n    final byte[] result;\n    SafeArray safeArray = getSafeArray(byteBuffer);\n    if (safeArray != null && safeArray.offset == 0 && safeArray.limit == safeArray.data.length) {\n      result = byteBuffer.array();\n    } else {\n      ByteBuffer toCopy = byteBuffer.asReadOnlyBuffer();\n      result = new byte[toCopy.limit()];\n      rewind(toCopy);\n      toCopy.get(result);\n    }\n    return result;\n  }\n\n  @NonNull\n  public static InputStream toStream(@NonNull ByteBuffer buffer) {\n    return new ByteBufferStream(buffer);\n  }\n\n  @NonNull\n  public static ByteBuffer fromStream(@NonNull InputStream stream) throws IOException {\n    ByteArrayOutputStream outStream = new ByteArrayOutputStream(BUFFER_SIZE);\n\n    byte[] buffer = BUFFER_REF.getAndSet(null);\n    if (buffer == null) {\n      buffer = new byte[BUFFER_SIZE];\n    }\n\n    int n;\n    while ((n = stream.read(buffer)) >= 0) {\n      outStream.write(buffer, 0, n);\n    }\n\n    BUFFER_REF.set(buffer);\n\n    byte[] bytes = outStream.toByteArray();\n\n    // Some resource decoders require a direct byte buffer. Prefer allocateDirect() over wrap()\n    return rewind(ByteBuffer.allocateDirect(bytes.length).put(bytes));\n  }\n\n  public static ByteBuffer rewind(ByteBuffer buffer) {\n    return (ByteBuffer) buffer.position(0);\n  }\n\n  @Nullable\n  private static SafeArray getSafeArray(@NonNull ByteBuffer byteBuffer) {\n    if (!byteBuffer.isReadOnly() && byteBuffer.hasArray()) {\n      return new SafeArray(byteBuffer.array(), byteBuffer.arrayOffset(), byteBuffer.limit());\n    }\n    return null;\n  }\n\n  static final class SafeArray {\n    @Synthetic final int offset;\n    @Synthetic final int limit;\n    @Synthetic final byte[] data;\n\n    // PMD.ArrayIsStoredDirectly Copying would be prohibitively expensive and/or lead to OOMs.\n    @SuppressWarnings(\"PMD.ArrayIsStoredDirectly\")\n    SafeArray(@NonNull byte[] data, int offset, int limit) {\n      this.data = data;\n      this.offset = offset;\n      this.limit = limit;\n    }\n  }\n\n  private static class ByteBufferStream extends InputStream {\n    private static final int UNSET = -1;\n    @NonNull private final ByteBuffer byteBuffer;\n    private int markPos = UNSET;\n\n    ByteBufferStream(@NonNull ByteBuffer byteBuffer) {\n      this.byteBuffer = byteBuffer;\n    }\n\n    @Override\n    public int available() {\n      return byteBuffer.remaining();\n    }\n\n    @Override\n    public int read() {\n      if (!byteBuffer.hasRemaining()) {\n        return -1;\n      }\n      return byteBuffer.get() & 0xFF;\n    }\n\n    @Override\n    public synchronized void mark(int readLimit) {\n      markPos = byteBuffer.position();\n    }\n\n    @Override\n    public boolean markSupported() {\n      return true;\n    }\n\n    @Override\n    public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) {\n      if (!byteBuffer.hasRemaining()) {\n        return -1;\n      }\n      int toRead = Math.min(byteCount, available());\n      byteBuffer.get(buffer, byteOffset, toRead);\n      return toRead;\n    }\n\n    @Override\n    public synchronized void reset() throws IOException {\n      if (markPos == UNSET) {\n        throw new IOException(\"Cannot reset to unset mark position\");\n      }\n      // reset() was not implemented correctly in 4.0.4, so we track the mark position ourselves.\n      byteBuffer.position(markPos);\n    }\n\n    @Override\n    public long skip(long byteCount) {\n      if (!byteBuffer.hasRemaining()) {\n        return -1;\n      }\n\n      long toSkip = Math.min(byteCount, available());\n      byteBuffer.position((int) (byteBuffer.position() + toSkip));\n      return toSkip;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/CachedHashCodeArrayMap.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.collection.ArrayMap;\nimport androidx.collection.SimpleArrayMap;\n\n/**\n * An {@link ArrayMap} that caches its hashCode to support efficient lookup.\n *\n * @param <K> the key type.\n * @param <V> the value type.\n */\n// We're overriding hashcode, but not in a way that changes the output, so we don't need to\n// override equals.\n@SuppressWarnings(\"PMD.OverrideBothEqualsAndHashcode\")\npublic final class CachedHashCodeArrayMap<K, V> extends ArrayMap<K, V> {\n\n  private int hashCode;\n\n  @Override\n  public void clear() {\n    hashCode = 0;\n    super.clear();\n  }\n\n  @Override\n  public V setValueAt(int index, V value) {\n    hashCode = 0;\n    return super.setValueAt(index, value);\n  }\n\n  @Override\n  public V put(K key, V value) {\n    hashCode = 0;\n    return super.put(key, value);\n  }\n\n  @Override\n  public void putAll(SimpleArrayMap<? extends K, ? extends V> simpleArrayMap) {\n    hashCode = 0;\n    super.putAll(simpleArrayMap);\n  }\n\n  @Override\n  public V removeAt(int index) {\n    hashCode = 0;\n    return super.removeAt(index);\n  }\n\n  @Override\n  public int hashCode() {\n    if (hashCode == 0) {\n      hashCode = super.hashCode();\n    }\n    return hashCode;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/ContentLengthInputStream.java",
    "content": "package com.bumptech.glide.util;\n\nimport android.text.TextUtils;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Uses the content length as the basis for the return value of {@link #available()} and verifies\n * that at least content length bytes are returned from the various read methods.\n */\npublic final class ContentLengthInputStream extends FilterInputStream {\n  private static final String TAG = \"ContentLengthStream\";\n  private static final int UNKNOWN = -1;\n\n  private final long contentLength;\n  private int readSoFar;\n\n  @NonNull\n  public static InputStream obtain(\n      @NonNull InputStream other, @Nullable String contentLengthHeader) {\n    return obtain(other, parseContentLength(contentLengthHeader));\n  }\n\n  @NonNull\n  public static InputStream obtain(@NonNull InputStream other, long contentLength) {\n    return new ContentLengthInputStream(other, contentLength);\n  }\n\n  private static int parseContentLength(@Nullable String contentLengthHeader) {\n    int result = UNKNOWN;\n    if (!TextUtils.isEmpty(contentLengthHeader)) {\n      try {\n        result = Integer.parseInt(contentLengthHeader);\n      } catch (NumberFormatException e) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG, \"failed to parse content length header: \" + contentLengthHeader, e);\n        }\n      }\n    }\n    return result;\n  }\n\n  private ContentLengthInputStream(@NonNull InputStream in, long contentLength) {\n    super(in);\n    this.contentLength = contentLength;\n  }\n\n  @Override\n  public synchronized int available() throws IOException {\n    return (int) Math.max(contentLength - readSoFar, in.available());\n  }\n\n  @Override\n  public synchronized int read() throws IOException {\n    int value = super.read();\n    checkReadSoFarOrThrow(value >= 0 ? 1 : -1);\n    return value;\n  }\n\n  @Override\n  public int read(byte[] buffer) throws IOException {\n    return read(buffer, 0 /*byteOffset*/, buffer.length /*byteCount*/);\n  }\n\n  @Override\n  public synchronized int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {\n    return checkReadSoFarOrThrow(super.read(buffer, byteOffset, byteCount));\n  }\n\n  private int checkReadSoFarOrThrow(int read) throws IOException {\n    if (read >= 0) {\n      readSoFar += read;\n    } else if (contentLength - readSoFar > 0) {\n      throw new IOException(\n          \"Failed to read all expected data\"\n              + \", expected: \"\n              + contentLength\n              + \", but read: \"\n              + readSoFar);\n    }\n    return read;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Queue;\n\n/**\n * An {@link java.io.InputStream} that catches {@link java.io.IOException}s during read and skip\n * calls and stores them so they can later be handled or thrown. This class is a workaround for a\n * framework issue where exceptions during reads while decoding bitmaps in {@link\n * android.graphics.BitmapFactory} can return partially decoded bitmaps.\n *\n * <p>See https://github.com/bumptech/glide/issues/126.\n *\n * @deprecated In some cases, callers may not handle getting 0 or -1 return values from methods,\n *     which can lead to infinite loops (see #4438). Use {@link ExceptionPassthroughInputStream}\n *     instead. This class will be deleted in a future version of Glide.\n */\n@Deprecated\npublic class ExceptionCatchingInputStream extends InputStream {\n\n  private static final Queue<ExceptionCatchingInputStream> QUEUE = Util.createQueue(0);\n\n  private InputStream wrapped;\n  private IOException exception;\n\n  @NonNull\n  public static ExceptionCatchingInputStream obtain(@NonNull InputStream toWrap) {\n    ExceptionCatchingInputStream result;\n    synchronized (QUEUE) {\n      result = QUEUE.poll();\n    }\n    if (result == null) {\n      result = new ExceptionCatchingInputStream();\n    }\n    result.setInputStream(toWrap);\n    return result;\n  }\n\n  // Exposed for testing.\n  static void clearQueue() {\n    while (!QUEUE.isEmpty()) {\n      QUEUE.remove();\n    }\n  }\n\n  ExceptionCatchingInputStream() {\n    // Do nothing.\n  }\n\n  void setInputStream(@NonNull InputStream toWrap) {\n    wrapped = toWrap;\n  }\n\n  @Override\n  public int available() throws IOException {\n    return wrapped.available();\n  }\n\n  @Override\n  public void close() throws IOException {\n    wrapped.close();\n  }\n\n  @Override\n  public void mark(int readLimit) {\n    wrapped.mark(readLimit);\n  }\n\n  @Override\n  public boolean markSupported() {\n    return wrapped.markSupported();\n  }\n\n  @Override\n  public int read(byte[] buffer) {\n    int read;\n    try {\n      read = wrapped.read(buffer);\n    } catch (IOException e) {\n      exception = e;\n      read = -1;\n    }\n    return read;\n  }\n\n  @Override\n  public int read(byte[] buffer, int byteOffset, int byteCount) {\n    int read;\n    try {\n      read = wrapped.read(buffer, byteOffset, byteCount);\n    } catch (IOException e) {\n      exception = e;\n      read = -1;\n    }\n    return read;\n  }\n\n  @Override\n  public synchronized void reset() throws IOException {\n    wrapped.reset();\n  }\n\n  @Override\n  public long skip(long byteCount) {\n    long skipped;\n    try {\n      skipped = wrapped.skip(byteCount);\n    } catch (IOException e) {\n      exception = e;\n      skipped = 0;\n    }\n    return skipped;\n  }\n\n  @Override\n  public int read() {\n    int result;\n    try {\n      result = wrapped.read();\n    } catch (IOException e) {\n      exception = e;\n      result = -1;\n    }\n    return result;\n  }\n\n  @Nullable\n  public IOException getException() {\n    return exception;\n  }\n\n  public void release() {\n    exception = null;\n    wrapped = null;\n    synchronized (QUEUE) {\n      QUEUE.offer(this);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/ExceptionPassthroughInputStream.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.annotation.GuardedBy;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Queue;\n\n/**\n * An {@link java.io.InputStream} that catches, stores and rethrows {@link java.io.IOException}s\n * during read and skip calls. This allows users of this API to handle the exception at a higher\n * level if the exception is swallowed by some intermediate library. This class is a workaround for\n * a framework issue where exceptions during reads while decoding bitmaps in {@link\n * android.graphics.BitmapFactory} can return partially decoded bitmaps.\n *\n * <p>Unlike the deprecated {@link ExceptionCatchingInputStream}, this class will both store and\n * re-throw any IOExceptions. Rethrowing works around bugs in wrapping streams that may not fully\n * obey the stream contract. This is really only useful if some middle layer is going to catch the\n * exception (like BitmapFactory) but we want to propagate the exception instead.\n *\n * <p>See https://github.com/bumptech/glide/issues/126 and #4438.\n */\npublic final class ExceptionPassthroughInputStream extends InputStream {\n\n  @GuardedBy(\"POOL\")\n  private static final Queue<ExceptionPassthroughInputStream> POOL = Util.createQueue(0);\n\n  private InputStream wrapped;\n  private IOException exception;\n\n  @NonNull\n  public static ExceptionPassthroughInputStream obtain(@NonNull InputStream toWrap) {\n    ExceptionPassthroughInputStream result;\n    synchronized (POOL) {\n      result = POOL.poll();\n    }\n    if (result == null) {\n      result = new ExceptionPassthroughInputStream();\n    }\n    result.setInputStream(toWrap);\n    return result;\n  }\n\n  // Exposed for testing.\n  static void clearQueue() {\n    synchronized (POOL) {\n      while (!POOL.isEmpty()) {\n        POOL.remove();\n      }\n    }\n  }\n\n  ExceptionPassthroughInputStream() {\n    // Do nothing.\n  }\n\n  void setInputStream(@NonNull InputStream toWrap) {\n    wrapped = toWrap;\n  }\n\n  @Override\n  public int available() throws IOException {\n    return wrapped.available();\n  }\n\n  @Override\n  public void close() throws IOException {\n    wrapped.close();\n  }\n\n  @Override\n  public void mark(int readLimit) {\n    wrapped.mark(readLimit);\n  }\n\n  @Override\n  public boolean markSupported() {\n    return wrapped.markSupported();\n  }\n\n  @Override\n  public int read() throws IOException {\n    try {\n      return wrapped.read();\n    } catch (IOException e) {\n      exception = e;\n      throw e;\n    }\n  }\n\n  @Override\n  public int read(byte[] buffer) throws IOException {\n    try {\n      return wrapped.read(buffer);\n    } catch (IOException e) {\n      exception = e;\n      throw e;\n    }\n  }\n\n  @Override\n  public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {\n    try {\n      return wrapped.read(buffer, byteOffset, byteCount);\n    } catch (IOException e) {\n      exception = e;\n      throw e;\n    }\n  }\n\n  @Override\n  public synchronized void reset() throws IOException {\n    wrapped.reset();\n  }\n\n  @Override\n  public long skip(long byteCount) throws IOException {\n    try {\n      return wrapped.skip(byteCount);\n    } catch (IOException e) {\n      exception = e;\n      throw e;\n    }\n  }\n\n  @Nullable\n  public IOException getException() {\n    return exception;\n  }\n\n  public void release() {\n    exception = null;\n    wrapped = null;\n    synchronized (POOL) {\n      POOL.offer(this);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/Executors.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\n\n/** Generic {@link Executor} implementations. */\npublic final class Executors {\n  private Executors() {\n    // Utility class.\n  }\n\n  private static final Executor MAIN_THREAD_EXECUTOR =\n      new Executor() {\n        @Override\n        public void execute(@NonNull Runnable command) {\n          Util.postOnUiThread(command);\n        }\n      };\n  private static final Executor MAIN_THREAD_EXECUTOR_FRONT =\n      new Executor() {\n        @Override\n        public void execute(@NonNull Runnable command) {\n          Util.postAtFrontOfQueueOnUiThread(command);\n        }\n      };\n  private static final Executor DIRECT_EXECUTOR =\n      new Executor() {\n        @Override\n        public void execute(@NonNull Runnable command) {\n          command.run();\n        }\n      };\n\n  /** Posts executions to the main thread. */\n  public static Executor mainThreadExecutor() {\n    return MAIN_THREAD_EXECUTOR;\n  }\n\n  /** Posts executions to the main thread at the front of the queue. */\n  public static Executor mainThreadExecutorFront() {\n    return MAIN_THREAD_EXECUTOR_FRONT;\n  }\n\n  /** Immediately calls {@link Runnable#run()} on the current thread. */\n  public static Executor directExecutor() {\n    return DIRECT_EXECUTOR;\n  }\n\n  @VisibleForTesting\n  public static void shutdownAndAwaitTermination(ExecutorService pool) {\n    long shutdownSeconds = 5;\n    pool.shutdownNow();\n    try {\n      if (!pool.awaitTermination(shutdownSeconds, TimeUnit.SECONDS)) {\n        pool.shutdownNow();\n        if (!pool.awaitTermination(shutdownSeconds, TimeUnit.SECONDS)) {\n          throw new RuntimeException(\"Failed to shutdown\");\n        }\n      }\n    } catch (InterruptedException ie) {\n      pool.shutdownNow();\n      Thread.currentThread().interrupt();\n      throw new RuntimeException(ie);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/FixedPreloadSizeProvider.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.ListPreloader;\n\n/**\n * A {@link com.bumptech.glide.ListPreloader.PreloadSizeProvider} with a fixed width and height.\n *\n * @param <T> The type of the model the size should be provided for.\n */\npublic class FixedPreloadSizeProvider<T> implements ListPreloader.PreloadSizeProvider<T> {\n\n  private final int[] size;\n\n  /**\n   * Constructor for a PreloadSizeProvider with a fixed size.\n   *\n   * @param width The width of the preload size in pixels.\n   * @param height The height of the preload size in pixels.\n   */\n  public FixedPreloadSizeProvider(int width, int height) {\n    this.size = new int[] {width, height};\n  }\n\n  @Nullable\n  @Override\n  // It's better to take on the risk that callers may mutate the array when there isn't any reason\n  // for them to do so than it the performance overhead of copying the array with every call.\n  @SuppressWarnings(\"PMD.MethodReturnsInternalArray\")\n  public int[] getPreloadSize(@NonNull T item, int adapterPosition, int itemPosition) {\n    return size;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/GlideSuppliers.java",
    "content": "package com.bumptech.glide.util;\n\n/** Similar to {@link com.google.common.base.Suppliers}, but named to reduce import confusion. */\npublic final class GlideSuppliers {\n  /**\n   * Produces a non-null instance of {@code T}.\n   *\n   * @param <T> The data type\n   */\n  public interface GlideSupplier<T> {\n    T get();\n  }\n\n  private GlideSuppliers() {}\n\n  public static <T> GlideSupplier<T> memorize(final GlideSupplier<T> supplier) {\n    return new GlideSupplier<T>() {\n      private volatile T instance;\n\n      @Override\n      public T get() {\n        if (instance == null) {\n          synchronized (this) {\n            if (instance == null) {\n              instance = Preconditions.checkNotNull(supplier.get());\n            }\n          }\n        }\n        return instance;\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/LogTime.java",
    "content": "package com.bumptech.glide.util;\n\nimport android.annotation.TargetApi;\nimport android.os.Build;\nimport android.os.SystemClock;\n\n/** A class for logging elapsed real time in millis. */\npublic final class LogTime {\n  private static final double MILLIS_MULTIPLIER =\n      Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? 1d / Math.pow(10, 6) : 1d;\n\n  private LogTime() {\n    // Utility class.\n  }\n\n  /**\n   * Returns the current time in either millis or nanos depending on the api level to be used with\n   * {@link #getElapsedMillis(long)}.\n   */\n  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n  public static long getLogTime() {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n      return SystemClock.elapsedRealtimeNanos();\n    } else {\n      return SystemClock.uptimeMillis();\n    }\n  }\n\n  /**\n   * Returns the time elapsed since the given logTime in millis.\n   *\n   * @param logTime The start time of the event.\n   */\n  public static double getElapsedMillis(long logTime) {\n    return (getLogTime() - logTime) * MILLIS_MULTIPLIER;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/LruCache.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * A general purpose size limited cache that evicts items using an LRU algorithm. By default every\n * item is assumed to have a size of one. Subclasses can override {@link #getSize(Object)}} to\n * change the size on a per item basis.\n *\n * @param <T> The type of the keys.\n * @param <Y> The type of the values.\n */\npublic class LruCache<T, Y> {\n  private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);\n  private final long initialMaxSize;\n  private long maxSize;\n  private long currentSize;\n\n  /**\n   * Constructor for LruCache.\n   *\n   * @param size The maximum size of the cache, the units must match the units used in {@link\n   *     #getSize(Object)}.\n   */\n  public LruCache(long size) {\n    this.initialMaxSize = size;\n    this.maxSize = size;\n  }\n\n  /**\n   * Sets a size multiplier that will be applied to the size provided in the constructor to put the\n   * new size of the cache. If the new size is less than the current size, entries will be evicted\n   * until the current size is less than or equal to the new size.\n   *\n   * @param multiplier The multiplier to apply.\n   */\n  public synchronized void setSizeMultiplier(float multiplier) {\n    if (multiplier < 0) {\n      throw new IllegalArgumentException(\"Multiplier must be >= 0\");\n    }\n    maxSize = Math.round(initialMaxSize * multiplier);\n    evict();\n  }\n\n  /**\n   * Returns the size of a given item, defaulting to one. The units must match those used in the\n   * size passed in to the constructor. Subclasses can override this method to return sizes in\n   * various units, usually bytes.\n   *\n   * @param item The item to get the size of.\n   */\n  protected int getSize(@Nullable Y item) {\n    return 1;\n  }\n\n  /** Returns the number of entries stored in cache. */\n  protected synchronized int getCount() {\n    return cache.size();\n  }\n\n  /**\n   * A callback called whenever an item is evicted from the cache. Subclasses can override.\n   *\n   * @param key The key of the evicted item.\n   * @param item The evicted item.\n   */\n  protected void onItemEvicted(@NonNull T key, @Nullable Y item) {\n    // optional override\n  }\n\n  /** Returns the current maximum size of the cache in bytes. */\n  public synchronized long getMaxSize() {\n    return maxSize;\n  }\n\n  /** Returns the sum of the sizes of all items in the cache. */\n  public synchronized long getCurrentSize() {\n    return currentSize;\n  }\n\n  /**\n   * Returns true if there is a value for the given key in the cache.\n   *\n   * @param key The key to check.\n   */\n  public synchronized boolean contains(@NonNull T key) {\n    return cache.containsKey(key);\n  }\n\n  /**\n   * Returns the item in the cache for the given key or null if no such item exists.\n   *\n   * @param key The key to check.\n   */\n  @Nullable\n  public synchronized Y get(@NonNull T key) {\n    Entry<Y> entry = cache.get(key);\n    return entry != null ? entry.value : null;\n  }\n\n  /**\n   * Adds the given item to the cache with the given key and returns any previous entry for the\n   * given key that may have already been in the cache.\n   *\n   * <p>If the size of the item is larger than the total cache size, the item will not be added to\n   * the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with\n   * the given key and item.\n   *\n   * <p>The size of the item is determined by the {@link #getSize(Object)} method. To avoid errors\n   * where {@link #getSize(Object)} returns different values for the same object when called at\n   * different times, the size value is acquired in {@code put} and retained until the item is\n   * evicted, replaced or removed.\n   *\n   * <p>If {@code item} is null the behavior here is a little odd. For the most part it's similar to\n   * simply calling {@link #remove(Object)} with the given key. The difference is that calling this\n   * method with a null {@code item} will result in an entry remaining in the cache with a null\n   * value and 0 size. The only real consequence is that at some point {@link #onItemEvicted(Object,\n   * Object)} may be called with the given {@code key} and a null value. Ideally we'd make calling\n   * this method with a null {@code item} identical to {@link #remove(Object)} but we're preserving\n   * this odd behavior to match older versions :(.\n   *\n   * @param key The key to add the item at.\n   * @param item The item to add.\n   */\n  @Nullable\n  public synchronized Y put(@NonNull T key, @Nullable Y item) {\n    final int itemSize = getSize(item);\n    if (itemSize >= maxSize) {\n      onItemEvicted(key, item);\n      return null;\n    }\n\n    if (item != null) {\n      currentSize += itemSize;\n    }\n    @Nullable Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));\n    if (old != null) {\n      currentSize -= old.size;\n\n      if (!old.value.equals(item)) {\n        onItemEvicted(key, old.value);\n      }\n    }\n    evict();\n\n    return old != null ? old.value : null;\n  }\n\n  /**\n   * Removes the item at the given key and returns the removed item if present, and null otherwise.\n   *\n   * @param key The key to remove the item at.\n   */\n  @Nullable\n  public synchronized Y remove(@NonNull T key) {\n    Entry<Y> entry = cache.remove(key);\n    if (entry == null) {\n      return null;\n    }\n    currentSize -= entry.size;\n    return entry.value;\n  }\n\n  /** Clears all items in the cache. */\n  public void clearMemory() {\n    trimToSize(0);\n  }\n\n  /**\n   * Removes the least recently used items from the cache until the current size is less than the\n   * given size.\n   *\n   * @param size The size the cache should be less than.\n   */\n  protected synchronized void trimToSize(long size) {\n    Map.Entry<T, Entry<Y>> last;\n    Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;\n    while (currentSize > size) {\n      cacheIterator = cache.entrySet().iterator();\n      last = cacheIterator.next();\n      final Entry<Y> toRemove = last.getValue();\n      currentSize -= toRemove.size;\n      final T key = last.getKey();\n      cacheIterator.remove();\n      onItemEvicted(key, toRemove.value);\n    }\n  }\n\n  private void evict() {\n    trimToSize(maxSize);\n  }\n\n  @Synthetic\n  static final class Entry<Y> {\n    final Y value;\n    final int size;\n\n    @Synthetic\n    Entry(Y value, int size) {\n      this.value = value;\n      this.size = size;\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/MarkEnforcingInputStream.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.annotation.NonNull;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Prevents {@link InputStream InputStreams} from overflowing their buffer by reading data past\n * their read limit.\n */\npublic class MarkEnforcingInputStream extends FilterInputStream {\n  private static final int UNSET = Integer.MIN_VALUE;\n  private static final int END_OF_STREAM = -1;\n\n  private int availableBytes = UNSET;\n\n  public MarkEnforcingInputStream(@NonNull InputStream in) {\n    super(in);\n  }\n\n  @Override\n  public synchronized void mark(int readLimit) {\n    super.mark(readLimit);\n    availableBytes = readLimit;\n  }\n\n  @Override\n  public int read() throws IOException {\n    if (getBytesToRead(1) == END_OF_STREAM) {\n      return END_OF_STREAM;\n    }\n\n    int result = super.read();\n    updateAvailableBytesAfterRead(1 /* bytesRead */);\n    return result;\n  }\n\n  @Override\n  public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {\n    int toRead = (int) getBytesToRead(byteCount);\n    if (toRead == END_OF_STREAM) {\n      return END_OF_STREAM;\n    }\n\n    int read = super.read(buffer, byteOffset, toRead);\n    updateAvailableBytesAfterRead(read);\n    return read;\n  }\n\n  @Override\n  public synchronized void reset() throws IOException {\n    super.reset();\n    availableBytes = UNSET;\n  }\n\n  @Override\n  public long skip(long byteCount) throws IOException {\n    long toSkip = getBytesToRead(byteCount);\n    if (toSkip == END_OF_STREAM) {\n      return 0;\n    }\n\n    long read = super.skip(toSkip);\n    updateAvailableBytesAfterRead(read);\n    return read;\n  }\n\n  @Override\n  public int available() throws IOException {\n    return availableBytes == UNSET\n        ? super.available()\n        : Math.min(availableBytes, super.available());\n  }\n\n  private long getBytesToRead(long targetByteCount) {\n    if (availableBytes == 0) {\n      return END_OF_STREAM;\n    } else if (availableBytes != UNSET && targetByteCount > availableBytes) {\n      return availableBytes;\n    } else {\n      return targetByteCount;\n    }\n  }\n\n  private void updateAvailableBytesAfterRead(long bytesRead) {\n    if (availableBytes != UNSET && bytesRead != END_OF_STREAM) {\n      // See https://errorprone.info/bugpattern/NarrowingCompoundAssignment.\n      availableBytes = (int) (availableBytes - bytesRead);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/MultiClassKey.java",
    "content": "package com.bumptech.glide.util;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\n/** A key of two {@link Class}es to be used in hashed collections. */\n@SuppressWarnings({\"PMD.ConstructorCallsOverridableMethod\"})\npublic class MultiClassKey {\n  private Class<?> first;\n  private Class<?> second;\n  private Class<?> third;\n\n  public MultiClassKey() {\n    // leave them null\n  }\n\n  public MultiClassKey(@NonNull Class<?> first, @NonNull Class<?> second) {\n    set(first, second);\n  }\n\n  public MultiClassKey(\n      @NonNull Class<?> first, @NonNull Class<?> second, @Nullable Class<?> third) {\n    set(first, second, third);\n  }\n\n  public void set(@NonNull Class<?> first, @NonNull Class<?> second) {\n    set(first, second, null);\n  }\n\n  public void set(@NonNull Class<?> first, @NonNull Class<?> second, @Nullable Class<?> third) {\n    this.first = first;\n    this.second = second;\n    this.third = third;\n  }\n\n  @Override\n  public String toString() {\n    return \"MultiClassKey{\" + \"first=\" + first + \", second=\" + second + '}';\n  }\n\n  @SuppressWarnings({\"PMD.SimplifyBooleanReturns\", \"RedundantIfStatement\"})\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    MultiClassKey that = (MultiClassKey) o;\n\n    if (!first.equals(that.first)) {\n      return false;\n    }\n    if (!second.equals(that.second)) {\n      return false;\n    }\n    if (!Util.bothNullOrEqual(third, that.third)) {\n      return false;\n    }\n\n    return true;\n  }\n\n  @Override\n  public int hashCode() {\n    int result = first.hashCode();\n    result = 31 * result + second.hashCode();\n    result = 31 * result + (third != null ? third.hashCode() : 0);\n    return result;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/Preconditions.java",
    "content": "package com.bumptech.glide.util;\n\nimport android.text.TextUtils;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.util.Collection;\n\n/** Contains common assertions. */\npublic final class Preconditions {\n\n  private Preconditions() {\n    // Utility class.\n  }\n\n  public static void checkArgument(boolean expression) {\n    checkArgument(expression, /* message= */ \"\");\n  }\n\n  public static void checkArgument(boolean expression, @NonNull String message) {\n    if (!expression) {\n      throw new IllegalArgumentException(message);\n    }\n  }\n\n  @NonNull\n  public static <T> T checkNotNull(@Nullable T arg) {\n    return checkNotNull(arg, \"Argument must not be null\");\n  }\n\n  @NonNull\n  public static <T> T checkNotNull(@Nullable T arg, @NonNull String message) {\n    if (arg == null) {\n      throw new NullPointerException(message);\n    }\n    return arg;\n  }\n\n  @NonNull\n  public static String checkNotEmpty(@Nullable String string) {\n    if (TextUtils.isEmpty(string)) {\n      throw new IllegalArgumentException(\"Must not be null or empty\");\n    }\n    return string;\n  }\n\n  @NonNull\n  public static <T extends Collection<Y>, Y> T checkNotEmpty(@NonNull T collection) {\n    if (collection.isEmpty()) {\n      throw new IllegalArgumentException(\"Must not be empty.\");\n    }\n    return collection;\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/Synthetic.java",
    "content": "package com.bumptech.glide.util;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/** Indicates that target's visibility can be relaxed to avoid synthetic methods. */\n@Retention(RetentionPolicy.SOURCE)\n@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})\npublic @interface Synthetic {}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/Util.java",
    "content": "package com.bumptech.glide.util;\n\nimport android.annotation.TargetApi;\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Looper;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.model.Model;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.request.target.Target;\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Queue;\n\n/** A collection of assorted utility classes. */\npublic final class Util {\n  private static final int HASH_MULTIPLIER = 31;\n  private static final int HASH_ACCUMULATOR = 17;\n  private static final char[] HEX_CHAR_ARRAY = \"0123456789abcdef\".toCharArray();\n  // 32 bytes from sha-256 -> 64 hex chars.\n  private static final char[] SHA_256_CHARS = new char[64];\n  @Nullable private static volatile Handler mainThreadHandler;\n\n  private Util() {\n    // Utility class.\n  }\n\n  /** Returns the hex string of the given byte array representing a SHA256 hash. */\n  @NonNull\n  public static String sha256BytesToHex(@NonNull byte[] bytes) {\n    synchronized (SHA_256_CHARS) {\n      return bytesToHex(bytes, SHA_256_CHARS);\n    }\n  }\n\n  // Taken from:\n  // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java\n  // /9655275#9655275\n  @SuppressWarnings(\"PMD.UseVarargs\")\n  @NonNull\n  private static String bytesToHex(@NonNull byte[] bytes, @NonNull char[] hexChars) {\n    int v;\n    for (int j = 0; j < bytes.length; j++) {\n      v = bytes[j] & 0xFF;\n      hexChars[j * 2] = HEX_CHAR_ARRAY[v >>> 4];\n      hexChars[j * 2 + 1] = HEX_CHAR_ARRAY[v & 0x0F];\n    }\n    return new String(hexChars);\n  }\n\n  /**\n   * Returns the allocated byte size of the given bitmap.\n   *\n   * @see #getBitmapByteSize(android.graphics.Bitmap)\n   * @deprecated Use {@link #getBitmapByteSize(android.graphics.Bitmap)} instead. Scheduled to be\n   *     removed in Glide 4.0.\n   */\n  @Deprecated\n  public static int getSize(@NonNull Bitmap bitmap) {\n    return getBitmapByteSize(bitmap);\n  }\n\n  /** Returns the in memory size of the given {@link Bitmap} in bytes. */\n  @TargetApi(Build.VERSION_CODES.KITKAT)\n  public static int getBitmapByteSize(@NonNull Bitmap bitmap) {\n    // The return value of getAllocationByteCount silently changes for recycled bitmaps from the\n    // internal buffer size to row bytes * height. To avoid random inconsistencies in caches, we\n    // instead assert here.\n    if (bitmap.isRecycled()) {\n      throw new IllegalStateException(\n          \"Cannot obtain size for recycled Bitmap: \"\n              + bitmap\n              + \"[\"\n              + bitmap.getWidth()\n              + \"x\"\n              + bitmap.getHeight()\n              + \"] \"\n              + bitmap.getConfig());\n    }\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n      // Workaround for KitKat initial release NPE in Bitmap, fixed in MR1. See issue #148.\n      try {\n        return bitmap.getAllocationByteCount();\n      } catch (\n          @SuppressWarnings(\"PMD.AvoidCatchingNPE\")\n          NullPointerException e) {\n        // Do nothing.\n      }\n    }\n    return bitmap.getHeight() * bitmap.getRowBytes();\n  }\n\n  /**\n   * Returns the in memory size of {@link android.graphics.Bitmap} with the given width, height, and\n   * {@link android.graphics.Bitmap.Config}.\n   */\n  public static int getBitmapByteSize(int width, int height, @Nullable Bitmap.Config config) {\n    return width * height * getBytesPerPixel(config);\n  }\n\n  /**\n   * Returns the number of bytes required to store each pixel of a {@link Bitmap} with the given\n   * {@code config}.\n   *\n   * <p>Defaults to {@link Bitmap.Config#ARGB_8888} if {@code config} is {@code null}.\n   */\n  public static int getBytesPerPixel(@Nullable Bitmap.Config config) {\n    // A bitmap by decoding a GIF has null \"config\" in certain environments.\n    if (config == null) {\n      config = Bitmap.Config.ARGB_8888;\n    }\n\n    int bytesPerPixel;\n    switch (config) {\n      case ALPHA_8:\n        bytesPerPixel = 1;\n        break;\n      case RGB_565:\n      case ARGB_4444:\n        bytesPerPixel = 2;\n        break;\n      case RGBA_F16:\n        bytesPerPixel = 8;\n        break;\n      case ARGB_8888:\n      default:\n        bytesPerPixel = 4;\n        break;\n    }\n    return bytesPerPixel;\n  }\n\n  /**\n   * Returns {@code true} if {@code width} and {@code height} are both {@code > 0} and/or equal to\n   * {@link Target#SIZE_ORIGINAL}.\n   */\n  public static boolean isValidDimensions(int width, int height) {\n    return isValidDimension(width) && isValidDimension(height);\n  }\n\n  public static boolean isValidDimension(int dimen) {\n    return dimen > 0 || dimen == Target.SIZE_ORIGINAL;\n  }\n\n  /** Posts the given {@code runnable} to the UI thread using a shared {@link Handler}. */\n  public static void postOnUiThread(Runnable runnable) {\n    getUiThreadHandler().post(runnable);\n  }\n\n  /**\n   * Posts the given {@code runnable} to the front of the queue on the UI thread using a shared\n   * {@link Handler}.\n   */\n  public static void postAtFrontOfQueueOnUiThread(Runnable runnable) {\n    getUiThreadHandler().postAtFrontOfQueue(runnable);\n  }\n\n  /** Removes the given {@code runnable} from the UI threads queue if it is still queued. */\n  public static void removeCallbacksOnUiThread(Runnable runnable) {\n    getUiThreadHandler().removeCallbacks(runnable);\n  }\n\n  private static Handler getUiThreadHandler() {\n    if (mainThreadHandler == null) {\n      synchronized (Util.class) {\n        if (mainThreadHandler == null) {\n          mainThreadHandler = new Handler(Looper.getMainLooper());\n        }\n      }\n    }\n    return mainThreadHandler;\n  }\n\n  /**\n   * Throws an {@link java.lang.IllegalArgumentException} if called on a thread other than the main\n   * thread.\n   */\n  public static void assertMainThread() {\n    if (!isOnMainThread()) {\n      throw new IllegalArgumentException(\"You must call this method on the main thread\");\n    }\n  }\n\n  /** Throws an {@link java.lang.IllegalArgumentException} if called on the main thread. */\n  public static void assertBackgroundThread() {\n    if (!isOnBackgroundThread()) {\n      throw new IllegalArgumentException(\"You must call this method on a background thread\");\n    }\n  }\n\n  /** Returns {@code true} if called on the main thread, {@code false} otherwise. */\n  public static boolean isOnMainThread() {\n    return Looper.myLooper() == Looper.getMainLooper();\n  }\n\n  /** Returns {@code true} if called on a background thread, {@code false} otherwise. */\n  public static boolean isOnBackgroundThread() {\n    return !isOnMainThread();\n  }\n\n  /** Creates a {@link java.util.Queue} of the given size using Glide's preferred implementation. */\n  @NonNull\n  public static <T> Queue<T> createQueue(int size) {\n    return new ArrayDeque<>(size);\n  }\n\n  /**\n   * Returns a copy of the given list that is safe to iterate over and perform actions that may\n   * modify the original list.\n   *\n   * <p>See #303, #375, #322, #2262.\n   */\n  @NonNull\n  @SuppressWarnings(\"UseBulkOperation\")\n  public static <T> List<T> getSnapshot(@NonNull Collection<T> other) {\n    // toArray creates a new ArrayList internally and does not guarantee that the values it contains\n    // are non-null. Collections.addAll in ArrayList uses toArray internally and therefore also\n    // doesn't guarantee that entries are non-null. WeakHashMap's iterator does avoid returning null\n    // and is therefore safe to use. See #322, #2262.\n    List<T> result = new ArrayList<>(other.size());\n    for (T item : other) {\n      if (item != null) {\n        result.add(item);\n      }\n    }\n    return result;\n  }\n\n  /**\n   * Null-safe equivalent of {@code a.equals(b)}.\n   *\n   * @see java.util.Objects#equals\n   */\n  public static boolean bothNullOrEqual(@Nullable Object a, @Nullable Object b) {\n    return a == null ? b == null : a.equals(b);\n  }\n\n  public static boolean bothModelsNullEquivalentOrEquals(@Nullable Object a, @Nullable Object b) {\n    if (a == null) {\n      return b == null;\n    }\n    if (a instanceof Model) {\n      return ((Model) a).isEquivalentTo(b);\n    }\n    return a.equals(b);\n  }\n\n  public static boolean bothBaseRequestOptionsNullEquivalentOrEquals(\n      @Nullable BaseRequestOptions<?> a, @Nullable BaseRequestOptions<?> b) {\n    if (a == null) {\n      return b == null;\n    }\n    return a.isEquivalentTo(b);\n  }\n\n  public static int hashCode(int value) {\n    return hashCode(value, HASH_ACCUMULATOR);\n  }\n\n  public static int hashCode(int value, int accumulator) {\n    return accumulator * HASH_MULTIPLIER + value;\n  }\n\n  public static int hashCode(float value) {\n    return hashCode(value, HASH_ACCUMULATOR);\n  }\n\n  public static int hashCode(float value, int accumulator) {\n    return hashCode(Float.floatToIntBits(value), accumulator);\n  }\n\n  public static int hashCode(@Nullable Object object, int accumulator) {\n    return hashCode(object == null ? 0 : object.hashCode(), accumulator);\n  }\n\n  public static int hashCode(boolean value, int accumulator) {\n    return hashCode(value ? 1 : 0, accumulator);\n  }\n\n  public static int hashCode(boolean value) {\n    return hashCode(value, HASH_ACCUMULATOR);\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/ViewPreloadSizeProvider.java",
    "content": "package com.bumptech.glide.util;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.ListPreloader;\nimport com.bumptech.glide.request.target.CustomViewTarget;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.transition.Transition;\nimport java.util.Arrays;\n\n/**\n * A {@link com.bumptech.glide.ListPreloader.PreloadSizeProvider} that will extract the preload size\n * from a given {@link android.view.View}.\n *\n * @param <T> The type of the model the size should be provided for.\n */\npublic class ViewPreloadSizeProvider<T>\n    implements ListPreloader.PreloadSizeProvider<T>, SizeReadyCallback {\n  private int[] size;\n\n  // We need to keep a strong reference to the Target so that it isn't garbage collected due to a\n  // weak reference\n  // while we're waiting to get its size.\n  @SuppressWarnings(\"unused\")\n  private SizeViewTarget viewTarget;\n\n  /**\n   * Constructor that does nothing by default and requires users to call {@link\n   * #setView(android.view.View)} when a View is available to registerComponents the dimensions\n   * returned by this class.\n   */\n  public ViewPreloadSizeProvider() {\n    // This constructor is intentionally empty. Nothing special is needed here.\n  }\n\n  /**\n   * Constructor that will extract the preload size from a given {@link android.view.View}.\n   *\n   * @param view A not null View the size will be extracted from async using an {@link\n   *     android.view.ViewTreeObserver .OnPreDrawListener}\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public ViewPreloadSizeProvider(@NonNull View view) {\n    viewTarget = new SizeViewTarget(view);\n    viewTarget.getSize(this);\n  }\n\n  @Nullable\n  @Override\n  public int[] getPreloadSize(@NonNull T item, int adapterPosition, int itemPosition) {\n    if (size == null) {\n      return null;\n    } else {\n      return Arrays.copyOf(size, size.length);\n    }\n  }\n\n  @Override\n  public void onSizeReady(int width, int height) {\n    size = new int[] {width, height};\n    viewTarget = null;\n  }\n\n  /**\n   * Sets the {@link android.view.View} the size will be extracted.\n   *\n   * <p>Note - only the first call to this method will be obeyed, subsequent requests will be\n   * ignored.\n   *\n   * @param view A not null View the size will be extracted async with an {@link\n   *     android.view.ViewTreeObserver .OnPreDrawListener}\n   */\n  public void setView(@NonNull View view) {\n    if (size != null || viewTarget != null) {\n      return;\n    }\n    viewTarget = new SizeViewTarget(view);\n    viewTarget.getSize(this);\n  }\n\n  static final class SizeViewTarget extends CustomViewTarget<View, Object> {\n    SizeViewTarget(@NonNull View view) {\n      super(view);\n    }\n\n    @Override\n    protected void onResourceCleared(@Nullable Drawable placeholder) {}\n\n    @Override\n    public void onLoadFailed(@Nullable Drawable errorDrawable) {}\n\n    @Override\n    public void onResourceReady(\n        @NonNull Object resource, @Nullable Transition<? super Object> transition) {}\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/pool/FactoryPools.java",
    "content": "package com.bumptech.glide.util.pool;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.core.util.Pools.Pool;\nimport androidx.core.util.Pools.SimplePool;\nimport androidx.core.util.Pools.SynchronizedPool;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Provides implementations of {@link Pool} never return {@code null}, log when new instances are\n * created, and that can use the {@link com.bumptech.glide.util.pool.FactoryPools.Poolable}\n * interface to ensure objects aren't used while inside the pool.\n */\npublic final class FactoryPools {\n  private static final String TAG = \"FactoryPools\";\n  private static final int DEFAULT_POOL_SIZE = 20;\n  private static final Resetter<Object> EMPTY_RESETTER =\n      new Resetter<Object>() {\n        @Override\n        public void reset(@NonNull Object object) {\n          // Do nothing.\n        }\n      };\n\n  private FactoryPools() {}\n\n  /**\n   * Returns a non-thread safe {@link Pool} that never returns {@code null} from {@link\n   * Pool#acquire()} and that contains objects of the type created by the given {@link Factory} with\n   * the given maximum size.\n   *\n   * <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will\n   * be used to create a new instance.\n   *\n   * @param <T> The type of object the pool will contains.\n   */\n  @NonNull\n  public static <T extends Poolable> Pool<T> simple(int size, @NonNull Factory<T> factory) {\n    return build(new SimplePool<T>(size), factory);\n  }\n\n  /**\n   * Identical to {@link #threadSafe(int, Factory, Resetter)} except no action is taken when an\n   * instance is returned to the pool.\n   */\n  @NonNull\n  public static <T extends Poolable> Pool<T> threadSafe(int size, @NonNull Factory<T> factory) {\n    return build(new SynchronizedPool<T>(size), factory);\n  }\n\n  /**\n   * Returns a new thread safe {@link Pool} that never returns {@code null} from {@link\n   * Pool#acquire()} and that contains objects of the type created by the given {@link Factory} with\n   * the given maximum size.\n   *\n   * <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will\n   * be used to create a new instance.\n   *\n   * <p>Each time an instance is returned to the pool {@code resetter} will be called with the given\n   * instance.\n   *\n   * @param <T> The type of object the pool will contains.\n   */\n  @NonNull\n  public static <T extends Poolable> Pool<T> threadSafe(\n      int size, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {\n    return build(new SynchronizedPool<T>(size), factory, resetter);\n  }\n\n  /**\n   * Returns a new {@link Pool} that never returns {@code null} and that contains {@link List Lists}\n   * of a specific generic type with a standard maximum size of 20.\n   *\n   * <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be\n   * created.\n   *\n   * @param <T> The type of object that the {@link List Lists} will contain.\n   */\n  @NonNull\n  public static <T> Pool<List<T>> threadSafeList() {\n    return threadSafeList(DEFAULT_POOL_SIZE);\n  }\n\n  /**\n   * Returns a new thread safe {@link Pool} that never returns {@code null} and that contains {@link\n   * List Lists} of a specific generic type with the given maximum size.\n   *\n   * <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be\n   * created.\n   *\n   * @param <T> The type of object that the {@link List Lists} will contain.\n   */\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  @NonNull\n  public static <T> Pool<List<T>> threadSafeList(int size) {\n    return build(\n        new SynchronizedPool<List<T>>(size),\n        new Factory<List<T>>() {\n          @NonNull\n          @Override\n          public List<T> create() {\n            return new ArrayList<>();\n          }\n        },\n        new Resetter<List<T>>() {\n          @Override\n          public void reset(@NonNull List<T> object) {\n            object.clear();\n          }\n        });\n  }\n\n  @NonNull\n  private static <T extends Poolable> Pool<T> build(\n      @NonNull Pool<T> pool, @NonNull Factory<T> factory) {\n    return build(pool, factory, FactoryPools.<T>emptyResetter());\n  }\n\n  @NonNull\n  private static <T> Pool<T> build(\n      @NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {\n    return new FactoryPool<>(pool, factory, resetter);\n  }\n\n  @NonNull\n  @SuppressWarnings(\"unchecked\")\n  private static <T> Resetter<T> emptyResetter() {\n    return (Resetter<T>) EMPTY_RESETTER;\n  }\n\n  /**\n   * Creates new instances of the given type.\n   *\n   * @param <T> The type of Object that will be created.\n   */\n  public interface Factory<T> {\n    T create();\n  }\n\n  /**\n   * Resets state when objects are returned to the pool.\n   *\n   * @param <T> The type of Object that will be reset.\n   */\n  public interface Resetter<T> {\n    void reset(@NonNull T object);\n  }\n\n  /**\n   * Allows additional verification to catch errors caused by using objects while they are in an\n   * object pool.\n   */\n  public interface Poolable {\n    @NonNull\n    StateVerifier getVerifier();\n  }\n\n  private static final class FactoryPool<T> implements Pool<T> {\n    private final Factory<T> factory;\n    private final Resetter<T> resetter;\n    private final Pool<T> pool;\n\n    FactoryPool(@NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {\n      this.pool = pool;\n      this.factory = factory;\n      this.resetter = resetter;\n    }\n\n    @Override\n    public T acquire() {\n      T result = pool.acquire();\n      if (result == null) {\n        result = factory.create();\n        if (Log.isLoggable(TAG, Log.VERBOSE)) {\n          Log.v(TAG, \"Created new \" + result.getClass());\n        }\n      }\n      if (result instanceof Poolable) {\n        ((Poolable) result).getVerifier().setRecycled(false /*isRecycled*/);\n      }\n      return result;\n    }\n\n    @Override\n    public boolean release(@NonNull T instance) {\n      if (instance instanceof Poolable) {\n        ((Poolable) instance).getVerifier().setRecycled(true /*isRecycled*/);\n      }\n      resetter.reset(instance);\n      return pool.release(instance);\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/pool/GlideTrace.java",
    "content": "package com.bumptech.glide.util.pool;\n\nimport androidx.tracing.Trace;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/** Systracing utilities for Glide. */\npublic final class GlideTrace {\n  // Enable this locally to see tracing statements.\n  private static final boolean TRACING_ENABLED = false;\n\n  private static final AtomicInteger COOKIE_CREATOR = TRACING_ENABLED ? new AtomicInteger() : null;\n\n  /** Maximum length of a systrace tag. */\n  private static final int MAX_LENGTH = 127;\n\n  private GlideTrace() {\n    // Utility class.\n  }\n\n  private static String truncateTag(String tag) {\n    if (tag.length() > MAX_LENGTH) {\n      return tag.substring(0, MAX_LENGTH - 1);\n    }\n    return tag;\n  }\n\n  public static void beginSection(String tag) {\n    if (TRACING_ENABLED) {\n      Trace.beginSection(truncateTag(tag));\n    }\n  }\n\n  public static void beginSectionFormat(String format, Object arg1) {\n    if (TRACING_ENABLED) {\n      Trace.beginSection(truncateTag(String.format(format, arg1)));\n    }\n  }\n\n  public static void beginSectionFormat(String format, Object arg1, Object arg2) {\n    if (TRACING_ENABLED) {\n      Trace.beginSection(truncateTag(String.format(format, arg1, arg2)));\n    }\n  }\n\n  public static void beginSectionFormat(String format, Object arg1, Object arg2, Object arg3) {\n    if (TRACING_ENABLED) {\n      Trace.beginSection(truncateTag(String.format(format, arg1, arg2, arg3)));\n    }\n  }\n\n  public static int beginSectionAsync(String tag) {\n    if (TRACING_ENABLED) {\n      int cookie = COOKIE_CREATOR.incrementAndGet();\n      Trace.beginAsyncSection(truncateTag(tag), cookie);\n      return cookie;\n    }\n    return -1;\n  }\n\n  public static void endSectionAsync(String tag, int cookie) {\n    if (TRACING_ENABLED) {\n      Trace.endAsyncSection(tag, cookie);\n    }\n  }\n\n  public static void endSection() {\n    if (TRACING_ENABLED) {\n      Trace.endSection();\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/java/com/bumptech/glide/util/pool/StateVerifier.java",
    "content": "package com.bumptech.glide.util.pool;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.util.Synthetic;\n\n/** Verifies that the job is not in the recycled state. */\npublic abstract class StateVerifier {\n  private static final boolean DEBUG = false;\n\n  /** Creates a new {@link StateVerifier} instance. */\n  @NonNull\n  public static StateVerifier newInstance() {\n    if (DEBUG) {\n      return new DebugStateVerifier();\n    } else {\n      return new DefaultStateVerifier();\n    }\n  }\n\n  private StateVerifier() {}\n\n  /**\n   * Throws an exception if we believe our object is recycled and inactive (i.e. is currently in an\n   * object pool).\n   */\n  public abstract void throwIfRecycled();\n\n  /** Sets whether or not our object is recycled. */\n  abstract void setRecycled(boolean isRecycled);\n\n  private static class DefaultStateVerifier extends StateVerifier {\n    private volatile boolean isReleased;\n\n    @Synthetic\n    DefaultStateVerifier() {}\n\n    @Override\n    public void throwIfRecycled() {\n      if (isReleased) {\n        throw new IllegalStateException(\"Already released\");\n      }\n    }\n\n    @Override\n    public void setRecycled(boolean isRecycled) {\n      this.isReleased = isRecycled;\n    }\n  }\n\n  private static class DebugStateVerifier extends StateVerifier {\n    // Keeps track of the stack trace where our state was set to recycled.\n    private volatile RuntimeException recycledAtStackTraceException;\n\n    @Synthetic\n    DebugStateVerifier() {}\n\n    @Override\n    public void throwIfRecycled() {\n      if (recycledAtStackTraceException != null) {\n        throw new IllegalStateException(\"Already released\", recycledAtStackTraceException);\n      }\n    }\n\n    @Override\n    void setRecycled(boolean isRecycled) {\n      if (isRecycled) {\n        recycledAtStackTraceException = new RuntimeException(\"Released\");\n      } else {\n        recycledAtStackTraceException = null;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <item name=\"glide_custom_view_target_tag\" type=\"id\"/>\n</resources>"
  },
  {
    "path": "library/src/test/java/com/bumptech/glide/request/target/CustomViewTargetTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNull;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Activity;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.view.View;\nimport android.view.View.OnAttachStateChangeListener;\nimport android.view.ViewGroup;\nimport android.view.ViewGroup.LayoutParams;\nimport android.view.ViewTreeObserver;\nimport android.widget.FrameLayout;\nimport android.widget.LinearLayout;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.google.common.truth.Truth;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.Robolectric;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.android.controller.ActivityController;\nimport org.robolectric.annotation.Config;\n\n/**\n * Test for {@link CustomViewTarget}.\n *\n * <p>TODO: This should really be in the tests subproject, but that causes errors because the R\n * class referenced in {@link CustomViewTarget} can't be found. This should be fixable with some\n * gradle changes, but I've so far failed to figure out the right set of commands.\n */\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = Config.OLDEST_SDK)\npublic class CustomViewTargetTest {\n  private ActivityController<Activity> activity;\n  private View view;\n  private ViewGroup parent;\n  private CustomViewTarget<View, Object> target;\n  @Mock private SizeReadyCallback cb;\n  @Mock private Request request;\n  private AttachStateTarget attachStateTarget;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    activity = Robolectric.buildActivity(Activity.class).create().start().postCreate(null).resume();\n    view = new View(activity.get());\n    target = new TestViewTarget(view);\n    attachStateTarget = new AttachStateTarget(view);\n\n    LinearLayout linearLayout = new LinearLayout(activity.get());\n    View expandView = new View(activity.get());\n    LinearLayout.LayoutParams linearLayoutParams =\n        new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, /* height= */ 0);\n    linearLayoutParams.weight = 1f;\n    expandView.setLayoutParams(linearLayoutParams);\n    linearLayout.addView(expandView);\n\n    parent = new FrameLayout(activity.get());\n    parent.addView(view);\n    linearLayout.addView(parent);\n\n    activity.get().setContentView(linearLayout);\n  }\n\n  @After\n  public void tearDown() {\n    CustomViewTarget.SizeDeterminer.maxDisplayLength = null;\n  }\n\n  @Test\n  public void testReturnsWrappedView() {\n    assertEquals(view, target.getView());\n  }\n\n  @Test\n  public void testReturnsNullFromGetRequestIfNoRequestSet() {\n    assertNull(target.getRequest());\n  }\n\n  @Test\n  public void testCanSetAndRetrieveRequest() {\n    target.setRequest(request);\n\n    assertEquals(request, target.getRequest());\n  }\n\n  @Test\n  public void testRetrievesRequestFromPreviousTargetForView() {\n    target.setRequest(request);\n\n    CustomViewTarget<View, Object> second = new TestViewTarget(view);\n\n    assertEquals(request, second.getRequest());\n  }\n\n  @Test\n  public void testSizeCallbackIsCalledSynchronouslyIfViewSizeSet() {\n    int dimens = 333;\n    // activity.get().setContentView(view);\n    view.layout(0, 0, dimens, dimens);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(eq(dimens), eq(dimens));\n  }\n\n  @Test\n  public void testSizeCallbackIsCalledSynchronouslyIfLayoutParamsConcreteSizeSet() {\n    int dimens = 444;\n    LayoutParams layoutParams = new FrameLayout.LayoutParams(dimens, dimens);\n    view.setLayoutParams(layoutParams);\n    view.requestLayout();\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(eq(dimens), eq(dimens));\n  }\n\n  @Config(qualifiers = \"w200dp-h300dp\")\n  @Test\n  public void getSize_withBothWrapContent_usesDisplayDimens() {\n    LayoutParams layoutParams =\n        new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);\n    view.setLayoutParams(layoutParams);\n\n    activity.visible();\n    view.layout(0, 0, 0, 0);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(300, 300);\n  }\n\n  @Config(qualifiers = \"w100dp-h200dp\")\n  @Test\n  public void getSize_withWrapContentWidthAndValidHeight_usesDisplayDimenAndValidHeight() {\n    int height = 100;\n    LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, height);\n    view.setLayoutParams(params);\n\n    activity.visible();\n    view.setRight(0);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(200, height);\n  }\n\n  @Config(qualifiers = \"w200dp-h100dp\")\n  @Test\n  public void getSize_withWrapContentHeightAndValidWidth_returnsWidthAndDisplayDimen() {\n    int width = 100;\n    LayoutParams params = new FrameLayout.LayoutParams(width, LayoutParams.WRAP_CONTENT);\n    view.setLayoutParams(params);\n    parent.getLayoutParams().height = 200;\n\n    activity.visible();\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(width, 200);\n  }\n\n  @Config(qualifiers = \"w500dp-h600dp\")\n  @Test\n  public void getSize_withWrapContentWidthAndMatchParentHeight_usesDisplayDimenWidthAndHeight() {\n    LayoutParams params =\n        new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);\n    view.setLayoutParams(params);\n\n    target.getSize(cb);\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n\n    int height = 32;\n    parent.getLayoutParams().height = height;\n    activity.visible();\n\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(cb).onSizeReady(500, height);\n  }\n\n  @Config(qualifiers = \"w300dp-h400dp\")\n  @Test\n  public void getSize_withMatchParentWidthAndWrapContentHeight_usesWidthAndDisplayDimenHeight() {\n    LayoutParams params =\n        new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);\n    view.setLayoutParams(params);\n\n    target.getSize(cb);\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n\n    int width = 32;\n    parent.getLayoutParams().width = 32;\n    activity.visible();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    if (Build.VERSION.SDK_INT <= 19) {\n      verify(cb).onSizeReady(width, 352);\n    } else {\n      verify(cb).onSizeReady(width, 344);\n    }\n  }\n\n  @Test\n  public void testMatchParentWidthAndHeight() {\n    LayoutParams params =\n        new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);\n    view.setLayoutParams(params);\n\n    target.getSize(cb);\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n\n    activity.visible();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(cb).onSizeReady(eq(parent.getWidth()), eq(parent.getHeight()));\n  }\n\n  @Test\n  public void testSizeCallbackIsCalledPreDrawIfNoDimensAndNoLayoutParams() {\n    target.getSize(cb);\n\n    int width = 12;\n    int height = 32;\n    parent.getLayoutParams().width = width;\n    parent.getLayoutParams().height = height;\n    activity.visible();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test\n  public void testSizeCallbacksAreCalledInOrderPreDraw() {\n    SizeReadyCallback[] cbs = new SizeReadyCallback[25];\n    for (int i = 0; i < cbs.length; i++) {\n      cbs[i] = mock(SizeReadyCallback.class);\n      target.getSize(cbs[i]);\n    }\n\n    int width = 100;\n    int height = 111;\n    parent.getLayoutParams().width = width;\n    parent.getLayoutParams().height = height;\n    activity.visible();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    InOrder order = inOrder((Object[]) cbs);\n    for (SizeReadyCallback cb : cbs) {\n      order.verify(cb).onSizeReady(eq(width), eq(height));\n    }\n  }\n\n  @Test\n  public void testDoesNotNotifyCallbackTwiceIfAddedTwice() {\n    target.getSize(cb);\n    target.getSize(cb);\n\n    view.setLayoutParams(new FrameLayout.LayoutParams(100, 100));\n    activity.visible();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(cb, times(1)).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void testDoesNotAddMultipleListenersIfMultipleCallbacksAreAdded() {\n    SizeReadyCallback cb1 = mock(SizeReadyCallback.class);\n    SizeReadyCallback cb2 = mock(SizeReadyCallback.class);\n    target.getSize(cb1);\n    target.getSize(cb2);\n    view.getViewTreeObserver().dispatchOnPreDraw();\n    // assertThat(shadowObserver.getPreDrawListeners()).hasSize(1);\n  }\n\n  @Test\n  public void testDoesAddSecondListenerIfFirstListenerIsRemovedBeforeSecondRequest() {\n    SizeReadyCallback cb1 = mock(SizeReadyCallback.class);\n    target.getSize(cb1);\n\n    view.setLayoutParams(new FrameLayout.LayoutParams(100, 100));\n    activity.visible();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    SizeReadyCallback cb2 = mock(SizeReadyCallback.class);\n    view.setLayoutParams(\n        new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));\n    target.getSize(cb2);\n\n    view.setLayoutParams(new FrameLayout.LayoutParams(100, 100));\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(cb2).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void testSizeCallbackIsNotCalledPreDrawIfNoDimensSetOnPreDraw() {\n    target.getSize(cb);\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n\n    activity.visible();\n    verify(cb).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void testSizeCallbackIsCalledPreDrawIfNoDimensAndNoLayoutParamsButLayoutParamsSetLater() {\n    target.getSize(cb);\n\n    int width = 689;\n    int height = 354;\n    LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);\n    view.setLayoutParams(layoutParams);\n    view.requestLayout();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test\n  public void testCallbackIsNotCalledTwiceIfPreDrawFiresTwice() {\n    activity.visible();\n    target.getSize(cb);\n\n    LayoutParams layoutParams = new FrameLayout.LayoutParams(1234, 4123);\n    view.setLayoutParams(layoutParams);\n    view.requestLayout();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(cb, times(1)).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void testCallbacksFromMultipleRequestsAreNotifiedOnPreDraw() {\n    SizeReadyCallback firstCb = mock(SizeReadyCallback.class);\n    SizeReadyCallback secondCb = mock(SizeReadyCallback.class);\n    target.getSize(firstCb);\n    target.getSize(secondCb);\n\n    int width = 68;\n    int height = 875;\n    LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);\n    view.setLayoutParams(layoutParams);\n    activity.visible();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n    view.getViewTreeObserver().dispatchOnPreDraw();\n\n    verify(firstCb, times(1)).onSizeReady(eq(width), eq(height));\n    verify(secondCb, times(1)).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test\n  public void testDoesNotThrowOnPreDrawIfViewTreeObserverIsDead() {\n    target.getSize(cb);\n\n    int width = 1;\n    int height = 2;\n    LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);\n    view.setLayoutParams(layoutParams);\n    ViewTreeObserver vto = view.getViewTreeObserver();\n    view.requestLayout();\n    activity.visible();\n    assertFalse(vto.isAlive());\n    vto.dispatchOnPreDraw();\n\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenNullView() {\n    new TestViewTarget(null);\n  }\n\n  @Test\n  public void testDecreasesDimensionsByViewPadding() {\n    activity.visible();\n    view.setLayoutParams(new FrameLayout.LayoutParams(100, 100));\n    view.setPadding(25, 25, 25, 25);\n    view.requestLayout();\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(50, 50);\n  }\n\n  @Test\n  public void getSize_withValidWidthAndHeight_notLaidOut_notLayoutRequested_callsSizeReady() {\n    view.setRight(100);\n    view.setBottom(100);\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(100, 100);\n  }\n\n  @Test\n  public void getSize_withLayoutParams_notLaidOut_doesCallSizeReady() {\n    view.setLayoutParams(new FrameLayout.LayoutParams(10, 10));\n    view.setRight(100);\n    view.setBottom(100);\n    target.getSize(cb);\n\n    verify(cb, times(1)).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void getSize_withLayoutParams_emptyParams_notLaidOutOrLayoutRequested_callsSizeReady() {\n    view.setLayoutParams(new FrameLayout.LayoutParams(0, 0));\n    view.setRight(100);\n    view.setBottom(100);\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(100, 100);\n  }\n\n  @Test\n  public void getSize_withValidWidthAndHeight_preV19_layoutRequested_callsSizeReady() {\n    view.setLayoutParams(new FrameLayout.LayoutParams(100, 100));\n    view.requestLayout();\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(100, 100);\n  }\n\n  @Test\n  public void getSize_withWidthAndHeightEqualToPadding_doesNotCallSizeReady() {\n    view.setLayoutParams(new FrameLayout.LayoutParams(100, 100));\n    view.requestLayout();\n    view.setPadding(50, 50, 50, 50);\n\n    target.getSize(cb);\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_withNullRequest_doesNothing() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(null);\n    activity.visible();\n  }\n\n  // This behavior isn't clearly correct, but it doesn't seem like there's any harm to clear an\n  // already cleared request, so we might as well avoid the extra check/complexity in the code.\n  @Test\n  public void clearOnDetach_onDetach_withClearedRequest_clearsRequest() {\n    activity.visible();\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    parent.removeView(view);\n\n    verify(request).clear();\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_withRunningRequest_pausesRequestOnce() {\n    activity.visible();\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    parent.removeView(view);\n\n    verify(request).clear();\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_afterOnLoadCleared_removesListener() {\n    activity.visible();\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n    attachStateTarget.setRequest(request);\n    parent.removeView(view);\n\n    verify(request, never()).clear();\n  }\n\n  @Test\n  public void clearOnDetach_moreThanOnce_registersObserverOnce() {\n    activity.visible();\n    attachStateTarget.setRequest(request);\n    attachStateTarget.clearOnDetach().clearOnDetach();\n    parent.removeView(view);\n\n    verify(request).clear();\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_afterMultipleClearOnDetaches_removesListener() {\n    activity.visible();\n    attachStateTarget.clearOnDetach().clearOnDetach().clearOnDetach();\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n    attachStateTarget.setRequest(request);\n    parent.removeView(view);\n\n    verify(request, never()).clear();\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_afterLoadCleared_clearsRequest() {\n    activity.visible();\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    parent.removeView(view);\n\n    verify(request).clear();\n  }\n\n  @Test\n  public void clearOnDetach_onAttach_withNullRequest_doesNothing() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(null);\n    activity.visible();\n  }\n\n  @Test\n  public void clearOnDetach_onAttach_withRunningRequest_doesNotBeginRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(false);\n    activity.visible();\n\n    verify(request, never()).begin();\n  }\n\n  @Test\n  public void clearOnDetach_onAttach_withClearedRequest_beginsRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    activity.visible();\n\n    verify(request).begin();\n  }\n\n  @Test\n  public void clearOnDetach_afterLoadClearedAndRestarted_onAttach_beginsRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n    attachStateTarget.onLoadStarted(/* placeholder= */ null);\n    activity.visible();\n\n    verify(request).begin();\n  }\n\n  @Test\n  public void clearOnDetach_onAttach_afterLoadCleared_doesNotBeingRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n    activity.visible();\n\n    verify(request, never()).begin();\n  }\n\n  @Test\n  public void onLoadStarted_withoutClearOnDetach_doesNotAddListener() {\n    activity.visible();\n    target.setRequest(request);\n    attachStateTarget.onLoadStarted(/* placeholder= */ null);\n    parent.removeView(view);\n\n    verify(request, never()).clear();\n  }\n\n  @Test\n  public void onLoadCleared_withoutClearOnDetach_doesNotRemoveListeners() {\n    final AtomicInteger count = new AtomicInteger();\n    OnAttachStateChangeListener expected =\n        new OnAttachStateChangeListener() {\n          @Override\n          public void onViewAttachedToWindow(View v) {\n            count.incrementAndGet();\n          }\n\n          @Override\n          public void onViewDetachedFromWindow(View v) {\n            // Intentionally Empty.\n          }\n        };\n    view.addOnAttachStateChangeListener(expected);\n\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n\n    activity.visible();\n\n    Truth.assertThat(count.get()).isEqualTo(1);\n  }\n\n  private static final class AttachStateTarget extends CustomViewTarget<View, Object> {\n    AttachStateTarget(View view) {\n      super(view);\n    }\n\n    @Override\n    protected void onResourceCleared(@Nullable Drawable placeholder) {\n      // Intentionally Empty.\n    }\n\n    @Override\n    public void onLoadFailed(@Nullable Drawable errorDrawable) {\n      // Intentionally Empty.\n    }\n\n    @Override\n    public void onResourceReady(\n        @NonNull Object resource, @Nullable Transition<? super Object> transition) {\n      // Intentionally Empty.\n    }\n  }\n\n  private static final class TestViewTarget extends CustomViewTarget<View, Object> {\n\n    TestViewTarget(View view) {\n      super(view);\n    }\n\n    @Override\n    protected void onResourceCleared(@Nullable Drawable placeholder) {\n      // Intentionally Empty.\n    }\n\n    // We're intentionally avoiding the super call.\n    @SuppressWarnings(\"MissingSuperCall\")\n    @Override\n    public void onResourceReady(\n        @NonNull Object resource, @Nullable Transition<? super Object> transition) {\n      // Avoid calling super.\n    }\n\n    // We're intentionally avoiding the super call.\n    @SuppressWarnings(\"MissingSuperCall\")\n    @Override\n    public void onResourceLoading(@Nullable Drawable placeholder) {\n      // Avoid calling super.\n    }\n\n    // We're intentionally avoiding the super call.\n    @SuppressWarnings(\"MissingSuperCall\")\n    @Override\n    public void onLoadFailed(@Nullable Drawable errorDrawable) {\n      // Avoid calling super.\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/build.gradle.kts",
    "content": "import com.android.build.gradle.LibraryExtension\nimport org.gradle.api.JavaVersion\nimport org.gradle.api.tasks.compile.JavaCompile\n\nplugins {\n    id(\"com.android.library\")\n}\n\ntasks.withType<JavaCompile>().configureEach {\n    options.setFork(true)\n}\n\nandroid {\n    (this as LibraryExtension).testOptions.unitTests.all { testTask ->\n        testTask.maxHeapSize = rootProject.extra.get(\"TEST_JVM_MEMORY_SIZE\") as String\n\n        if (JavaVersion.current() <= JavaVersion.VERSION_1_8) {\n            testTask.jvmArgs(\"-XX:MaxPermSize=${rootProject.extra.get(\"TEST_JVM_MEMORY_SIZE\")}\")\n        }\n\n        testTask.maxParallelForks = 2\n    }\n\n    namespace = \"com.bumptech.glide.test\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n\n    testOptions.unitTests.isIncludeAndroidResources = true\n\n    sourceSets {\n        getByName(\"androidTest\") {\n            resources.srcDirs(files(\"../../exifsamples\"))\n        }\n        getByName(\"test\") {\n            resources.srcDirs(files(\"../../exifsamples\"))\n        }\n    }\n}\n\ndependencies {\n    testImplementation(libs.androidx.appcompat)\n    testImplementation(project(\":library\"))\n    testImplementation(project(\":mocks\"))\n    testImplementation(project(\":testutil\"))\n    testImplementation(libs.guava.testlib)\n    testImplementation(libs.truth)\n    testImplementation(libs.junit)\n    testImplementation(libs.mockito.core)\n    testImplementation(libs.robolectric)\n    testImplementation(libs.mockwebserver)\n    testImplementation(libs.androidx.test.core)\n    testImplementation(libs.androidx.junit)\n    testImplementation(libs.androidx.test.runner)\n}\n\nafterEvaluate {\n    tasks.named(\"lint\").configure { enabled = false }\n    tasks.named(\"compileReleaseJavaWithJavac\").configure { enabled = false }\n    tasks.named(\"compileDebugJavaWithJavac\").configure { enabled = false }\n}"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/GlideContextTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.mock;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide.RequestOptionsFactory;\nimport com.bumptech.glide.load.engine.Engine;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.ImageViewTargetFactory;\nimport com.bumptech.glide.util.GlideSuppliers.GlideSupplier;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic final class GlideContextTest {\n  private Map<Class<?>, TransitionOptions<?, ?>> transitionOptions;\n  private GlideContext context;\n\n  @Before\n  public void setUp() {\n    Application app = ApplicationProvider.getApplicationContext();\n\n    transitionOptions = new HashMap<>();\n    context =\n        new GlideContext(\n            app,\n            new LruArrayPool(),\n            new GlideSupplier<Registry>() {\n              @Override\n              public Registry get() {\n                return new Registry();\n              }\n            },\n            new ImageViewTargetFactory(),\n            new RequestOptionsFactory() {\n              @NonNull\n              @Override\n              public RequestOptions build() {\n                return new RequestOptions();\n              }\n            },\n            transitionOptions,\n            /* defaultRequestListeners= */ Collections.<RequestListener<Object>>emptyList(),\n            mock(Engine.class),\n            mock(GlideExperiments.class),\n            Log.DEBUG);\n  }\n\n  @Test\n  public void getDefaultTransitionOptions_withNoOptionsRegistered_returnsDefaultOptions() {\n    assertThat(context.getDefaultTransitionOptions(Object.class))\n        .isEqualTo(GlideContext.DEFAULT_TRANSITION_OPTIONS);\n  }\n\n  @Test\n  public void getDefaultTransitionOptions_withNonMatchingOptionRegistered_returnsDefaultOptions() {\n    transitionOptions.put(Bitmap.class, new GenericTransitionOptions<>());\n    assertThat(context.getDefaultTransitionOptions(Drawable.class))\n        .isEqualTo(GlideContext.DEFAULT_TRANSITION_OPTIONS);\n  }\n\n  @Test\n  public void getDefaultTransitionOptions_withMatchingOptionsRegistered_returnsMatchingOptions() {\n    GenericTransitionOptions<Object> expected = new GenericTransitionOptions<>();\n    transitionOptions.put(Bitmap.class, expected);\n    assertThat(context.getDefaultTransitionOptions(Bitmap.class)).isEqualTo(expected);\n  }\n\n  @Test\n  public void getDefaultTransitionOptions_withSuperClassRegistered_returnsSuperClassOptions() {\n    DrawableTransitionOptions expected = new DrawableTransitionOptions();\n    transitionOptions.put(Drawable.class, expected);\n    assertThat(context.getDefaultTransitionOptions(BitmapDrawable.class)).isEqualTo(expected);\n    assertThat(context.getDefaultTransitionOptions(GifDrawable.class)).isEqualTo(expected);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/GlideTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.request.RequestOptions.decodeTypeOf;\nimport static com.bumptech.glide.request.RequestOptions.errorOf;\nimport static com.bumptech.glide.request.RequestOptions.placeholderOf;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.ArgumentMatchers.notNull;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.robolectric.annotation.LooperMode.Mode.LEGACY;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.AssetFileDescriptor;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.MockGlideExecutor;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.manager.Lifecycle;\nimport com.bumptech.glide.manager.RequestManagerTreeNode;\nimport com.bumptech.glide.module.GlideModule;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.CustomTarget;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.tests.TearDownGlide;\nimport com.bumptech.glide.tests.Util;\nimport com.bumptech.glide.testutil.TestResourceUtil;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.ByteBuffer;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\nimport org.robolectric.annotation.LooperMode;\nimport org.robolectric.annotation.Resetter;\nimport org.robolectric.shadow.api.Shadow;\n\n/** Tests for the {@link Glide} interface and singleton. */\n@LooperMode(LEGACY)\n@RunWith(RobolectricTestRunner.class)\n@Config(\n    sdk = ROBOLECTRIC_SDK,\n    shadows = {\n      GlideTest.ShadowFileDescriptorContentResolver.class,\n    })\n@SuppressWarnings(\"unchecked\")\npublic class GlideTest {\n  // Fixes method overload confusion.\n  private static final Object NULL = null;\n\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n\n  @SuppressWarnings(\"rawtypes\")\n  @Mock\n  private Target target;\n\n  @Mock private DiskCache.Factory diskCacheFactory;\n  @Mock private DiskCache diskCache;\n  @Mock private MemoryCache memoryCache;\n  @Mock private Lifecycle lifecycle;\n  @Mock private RequestManagerTreeNode treeNode;\n  @Mock private BitmapPool bitmapPool;\n\n  private ImageView imageView;\n  private RequestManager requestManager;\n  private Context context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n\n    // Run all tasks on the main thread so they complete synchronously.\n    GlideExecutor executor = MockGlideExecutor.newMainThreadExecutor();\n    when(diskCacheFactory.build()).thenReturn(diskCache);\n    Glide.init(\n        context,\n        new GlideBuilder()\n            .setMemoryCache(memoryCache)\n            .setDiskCache(diskCacheFactory)\n            .setSourceExecutor(executor)\n            .setDiskCacheExecutor(executor));\n    Registry registry = Glide.get(context).getRegistry();\n    registerMockModelLoader(\n        GlideUrl.class, InputStream.class, new ByteArrayInputStream(new byte[0]), registry);\n    registerMockModelLoader(\n        File.class, InputStream.class, new ByteArrayInputStream(new byte[0]), registry);\n    registerMockModelLoader(\n        File.class, ParcelFileDescriptor.class, mock(ParcelFileDescriptor.class), registry);\n    registerMockModelLoader(File.class, ByteBuffer.class, ByteBuffer.allocate(10), registry);\n\n    // Ensure that target's size ready callback will be called synchronously.\n    imageView = new ImageView(context);\n    imageView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));\n    imageView.layout(0, 0, 100, 100);\n    doAnswer(new CallSizeReady()).when(target).getSize(isA(SizeReadyCallback.class));\n\n    requestManager = new RequestManager(Glide.get(context), lifecycle, treeNode, context);\n    requestManager.resumeRequests();\n  }\n\n  @Test\n  public void testCanSetMemoryCategory() {\n    MemoryCategory memoryCategory = MemoryCategory.NORMAL;\n    Glide glide = buildGlideWithFakePools();\n    glide.setMemoryCategory(memoryCategory);\n\n    verify(memoryCache).setSizeMultiplier(eq(memoryCategory.getMultiplier()));\n    verify(bitmapPool).setSizeMultiplier(eq(memoryCategory.getMultiplier()));\n  }\n\n  @Test\n  public void testCanIncreaseMemoryCategory() {\n    MemoryCategory memoryCategory = MemoryCategory.NORMAL;\n    Glide glide = buildGlideWithFakePools();\n    glide.setMemoryCategory(memoryCategory);\n\n    verify(memoryCache).setSizeMultiplier(eq(memoryCategory.getMultiplier()));\n    verify(bitmapPool).setSizeMultiplier(eq(memoryCategory.getMultiplier()));\n\n    MemoryCategory newMemoryCategory = MemoryCategory.HIGH;\n    MemoryCategory oldMemoryCategory = glide.setMemoryCategory(newMemoryCategory);\n\n    assertEquals(memoryCategory, oldMemoryCategory);\n\n    verify(memoryCache).setSizeMultiplier(eq(newMemoryCategory.getMultiplier()));\n    verify(bitmapPool).setSizeMultiplier(eq(newMemoryCategory.getMultiplier()));\n  }\n\n  @Test\n  public void testCanDecreaseMemoryCategory() {\n    MemoryCategory memoryCategory = MemoryCategory.NORMAL;\n    Glide glide = buildGlideWithFakePools();\n    glide.setMemoryCategory(memoryCategory);\n\n    verify(memoryCache).setSizeMultiplier(eq(memoryCategory.getMultiplier()));\n    verify(bitmapPool).setSizeMultiplier(eq(memoryCategory.getMultiplier()));\n\n    MemoryCategory newMemoryCategory = MemoryCategory.LOW;\n    MemoryCategory oldMemoryCategory = glide.setMemoryCategory(newMemoryCategory);\n\n    assertEquals(memoryCategory, oldMemoryCategory);\n\n    verify(memoryCache).setSizeMultiplier(eq(newMemoryCategory.getMultiplier()));\n    verify(bitmapPool).setSizeMultiplier(eq(newMemoryCategory.getMultiplier()));\n  }\n\n  @Test\n  public void testClearMemory() {\n    Glide glide = buildGlideWithFakePools();\n\n    glide.clearMemory();\n\n    verify(bitmapPool).clearMemory();\n    verify(memoryCache).clearMemory();\n  }\n\n  @Test\n  public void testTrimMemory() {\n    Glide glide = buildGlideWithFakePools();\n\n    final int level = 123;\n\n    glide.trimMemory(level);\n\n    verify(bitmapPool).trimMemory(eq(level));\n    verify(memoryCache).trimMemory(eq(level));\n  }\n\n  private Glide buildGlideWithFakePools() {\n    return new GlideBuilder()\n        .setBitmapPool(bitmapPool)\n        .setMemoryCache(memoryCache)\n        .build(\n            context,\n            Collections.<GlideModule>emptyList(),\n            /* annotationGeneratedGlideModule= */ null);\n  }\n\n  @Test\n  public void testFileDefaultLoaderWithInputStream() {\n    registerFailFactory(File.class, ParcelFileDescriptor.class);\n    runTestFileDefaultLoader();\n  }\n\n  @Test\n  public void testFileDefaultLoaderWithFileDescriptor() {\n    registerFailFactory(File.class, InputStream.class);\n    runTestFileDefaultLoader();\n  }\n\n  @Test\n  public void testFileDefaultLoader() {\n    runTestFileDefaultLoader();\n  }\n\n  private void runTestFileDefaultLoader() {\n    File file = new File(\"fake\");\n    mockUri(Uri.fromFile(file));\n\n    requestManager.load(file).into(target);\n    requestManager.load(file).into(imageView);\n\n    verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class));\n    verify(target).setRequest((Request) notNull());\n\n    assertNotNull(imageView.getDrawable());\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  @Test\n  public void testUrlDefaultLoader() throws MalformedURLException {\n    URL url = new URL(\"http://www.google.com\");\n\n    requestManager.load(url).into(target);\n    requestManager.load(url).into(imageView);\n\n    verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class));\n    verify(target).setRequest((Request) notNull());\n\n    assertNotNull(imageView.getDrawable());\n  }\n\n  @Test\n  public void testAsBitmapOption() {\n    Uri uri = Uri.parse(\"content://something/else\");\n    mockUri(uri);\n\n    requestManager.asBitmap().load(uri).into(target);\n\n    verify(target).onResourceReady(isA(Bitmap.class), isA(Transition.class));\n  }\n\n  @Test\n  public void testToBytesOption() {\n    Uri uri = Uri.parse(\"content://something/else\");\n    mockUri(uri);\n\n    requestManager.as(byte[].class).apply(decodeTypeOf(Bitmap.class)).load(uri).into(target);\n\n    verify(target).onResourceReady(isA(byte[].class), isA(Transition.class));\n  }\n\n  @Test\n  public void testLoadColorDrawable_withUnitBitmapTransformation_returnsColorDrawable() {\n    ColorDrawable colorDrawable = new ColorDrawable(Color.RED);\n    requestManager\n        .load(colorDrawable)\n        .apply(new RequestOptions().override(100, 100).centerCrop())\n        .into(target);\n\n    ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class);\n    verify(target).onResourceReady(argumentCaptor.capture(), isA(Transition.class));\n\n    Object result = argumentCaptor.getValue();\n\n    assertThat(result).isInstanceOf(ColorDrawable.class);\n    assertThat(((ColorDrawable) result).getColor()).isEqualTo(Color.RED);\n  }\n\n  @Test\n  public void testLoadColorDrawable_withNonUnitBitmapTransformation_returnsBitmapDrawable() {\n    ColorDrawable colorDrawable = new ColorDrawable(Color.RED);\n    requestManager\n        .load(colorDrawable)\n        .apply(new RequestOptions().override(100, 100).circleCrop())\n        .into(target);\n\n    ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class);\n    verify(target).onResourceReady(argumentCaptor.capture(), isA(Transition.class));\n\n    Object result = argumentCaptor.getValue();\n\n    assertThat(result).isInstanceOf(BitmapDrawable.class);\n    Bitmap bitmap = ((BitmapDrawable) result).getBitmap();\n    assertThat(bitmap.getWidth()).isEqualTo(100);\n    assertThat(bitmap.getHeight()).isEqualTo(100);\n  }\n\n  @Test\n  public void testUriDefaultLoaderWithInputStream() {\n    registerFailFactory(Uri.class, ParcelFileDescriptor.class);\n    runTestUriDefaultLoader();\n  }\n\n  @Test\n  public void testUriDefaultLoaderWithFileDescriptor() {\n    registerFailFactory(Uri.class, InputStream.class);\n    runTestUriDefaultLoader();\n  }\n\n  @Test\n  public void testUriDefaultLoader() {\n    runTestUriDefaultLoader();\n  }\n\n  private void runTestUriDefaultLoader() {\n    Uri uri = Uri.parse(\"content://test/something\");\n    mockUri(uri);\n\n    requestManager.load(uri).into(target);\n    requestManager.load(uri).into(imageView);\n\n    verify(target).onResourceReady(notNull(), isA(Transition.class));\n    verify(target).setRequest((Request) notNull());\n\n    assertNotNull(imageView.getDrawable());\n  }\n\n  @Test\n  public void testStringDefaultLoaderWithUrl() {\n    runTestStringDefaultLoader(\"http://www.google.com\");\n  }\n\n  @Test\n  public void testFileStringDefaultLoaderWithInputStream() {\n    registerFailFactory(String.class, ParcelFileDescriptor.class);\n    runTestFileStringDefaultLoader();\n  }\n\n  @Test\n  public void testFileStringDefaultLoaderWithFileDescriptor() {\n    registerFailFactory(String.class, ParcelFileDescriptor.class);\n    runTestFileStringDefaultLoader();\n  }\n\n  @Test\n  public void testFileStringDefaultLoader() {\n    runTestFileStringDefaultLoader();\n  }\n\n  private void runTestFileStringDefaultLoader() {\n    String path = \"/some/random/path\";\n    mockUri(Uri.fromFile(new File(path)));\n    runTestStringDefaultLoader(path);\n  }\n\n  @Test\n  public void testUriStringDefaultLoaderWithInputStream() {\n    registerFailFactory(String.class, ParcelFileDescriptor.class);\n    runTestUriStringDefaultLoader();\n  }\n\n  @Test\n  public void testUriStringDefaultLoaderWithFileDescriptor() {\n    registerFailFactory(String.class, InputStream.class);\n    runTestUriStringDefaultLoader();\n  }\n\n  @Test\n  public void testUriStringDefaultLoader() {\n    runTestUriStringDefaultLoader();\n  }\n\n  private void runTestUriStringDefaultLoader() {\n    String stringUri = \"content://some/random/uri\";\n    mockUri(Uri.parse(stringUri));\n    runTestStringDefaultLoader(stringUri);\n  }\n\n  private void runTestStringDefaultLoader(String string) {\n    requestManager\n        .load(string)\n        .listener(\n            new RequestListener<Drawable>() {\n              @Override\n              public boolean onLoadFailed(\n                  GlideException e,\n                  Object model,\n                  @NonNull Target<Drawable> target,\n                  boolean isFirstResource) {\n                throw new RuntimeException(\"Load failed\");\n              }\n\n              @Override\n              public boolean onResourceReady(\n                  @NonNull Drawable resource,\n                  @NonNull Object model,\n                  Target<Drawable> target,\n                  @NonNull DataSource dataSource,\n                  boolean isFirstResource) {\n                return false;\n              }\n            })\n        .into(target);\n    requestManager.load(string).into(imageView);\n\n    verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class));\n    verify(target).setRequest((Request) notNull());\n\n    assertNotNull(imageView.getDrawable());\n  }\n\n  @Test\n  public void testIntegerDefaultLoaderWithInputStream() {\n    registerFailFactory(Integer.class, ParcelFileDescriptor.class);\n    runTestIntegerDefaultLoader();\n  }\n\n  @Test\n  public void testIntegerDefaultLoaderWithFileDescriptor() {\n    registerFailFactory(Integer.class, InputStream.class);\n    runTestIntegerDefaultLoader();\n  }\n\n  @Test\n  public void testIntegerDefaultLoader() {\n    runTestIntegerDefaultLoader();\n  }\n\n  private void runTestIntegerDefaultLoader() {\n    int integer = android.R.drawable.star_on;\n    mockUri(\"android.resource://\" + \"android\" + \"/drawable/star_on\");\n\n    requestManager.load(integer).into(target);\n    requestManager.load(integer).into(imageView);\n\n    verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class));\n    verify(target).setRequest((Request) notNull());\n\n    assertNotNull(imageView.getDrawable());\n  }\n\n  @Test\n  public void testByteArrayDefaultLoader() {\n    byte[] bytes = new byte[10];\n    requestManager.load(bytes).into(target);\n    requestManager.load(bytes).into(imageView);\n\n    verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class));\n    verify(target).setRequest((Request) notNull());\n\n    assertNotNull(imageView.getDrawable());\n  }\n\n  @Test(expected = Exception.class)\n  public void testUnregisteredModelThrowsException() {\n    Float unregistered = 0.5f;\n    requestManager.load(unregistered).into(target);\n  }\n\n  @Test\n  @SuppressWarnings(\"unchecked\")\n  public void testNonDefaultModelWithRegisteredFactoryDoesNotThrow() {\n    registerMockStreamModelLoader(Float.class);\n\n    requestManager.load(0.5f).into(target);\n  }\n\n  @Test\n  public void testReceivesGif() {\n    String fakeUri = \"content://fake\";\n    InputStream testGifData = openGif();\n    mockUri(Uri.parse(fakeUri), testGifData);\n\n    requestManager.asGif().load(fakeUri).into(target);\n\n    verify(target).onResourceReady(isA(GifDrawable.class), isA(Transition.class));\n  }\n\n  @Test\n  public void testReceivesGifBytes() {\n    String fakeUri = \"content://fake\";\n    InputStream testGifData = openGif();\n    mockUri(Uri.parse(fakeUri), testGifData);\n\n    requestManager\n        .as(byte[].class)\n        .apply(decodeTypeOf(GifDrawable.class))\n        .load(fakeUri)\n        .into(target);\n\n    verify(target).onResourceReady(isA(byte[].class), isA(Transition.class));\n  }\n\n  @Test\n  public void testReceivesBitmapBytes() {\n    String fakeUri = \"content://fake\";\n    mockUri(fakeUri);\n    requestManager.as(byte[].class).apply(decodeTypeOf(Bitmap.class)).load(fakeUri).into(target);\n\n    verify(target).onResourceReady(isA(byte[].class), isA(Transition.class));\n  }\n\n  @Test\n  public void testReceivesThumbnails() {\n    String full = mockUri(\"content://full\");\n    String thumb = mockUri(\"content://thumb\");\n    requestManager.load(full).thumbnail(requestManager.load(thumb)).into(target);\n\n    verify(target, times(2)).onResourceReady(isA(Drawable.class), isA(Transition.class));\n  }\n\n  @Test\n  public void testReceivesRecursiveThumbnails() {\n    requestManager\n        .load(mockUri(\"content://first\"))\n        .thumbnail(\n            requestManager\n                .load(mockUri(\"content://second\"))\n                .thumbnail(\n                    requestManager\n                        .load(mockUri(\"content://third\"))\n                        .thumbnail(requestManager.load(mockUri(\"content://fourth\")))))\n        .into(target);\n    verify(target, times(4)).onResourceReady(isA(Drawable.class), isA(Transition.class));\n  }\n\n  @Test\n  public void testReceivesRecursiveThumbnailWithPercentage() {\n    requestManager\n        .load(mockUri(\"content://first\"))\n        .thumbnail(requestManager.load(mockUri(\"content://second\")).thumbnail(0.5f))\n        .into(target);\n    verify(target, times(3)).onResourceReady(isA(Drawable.class), isA(Transition.class));\n  }\n\n  @Test\n  public void testNullModelInGenericImageLoadDoesNotThrow() {\n    requestManager.load(NULL).into(target);\n  }\n\n  @Test\n  public void testNullModelInGenericVideoLoadDoesNotThrow() {\n    requestManager.load(NULL).into(target);\n  }\n\n  @Test\n  public void testNullModelInGenericLoadDoesNotThrow() {\n    requestManager.load(NULL).into(target);\n  }\n\n  @Test\n  public void testNullModelDoesNotThrow() {\n    Drawable drawable = new ColorDrawable(Color.RED);\n    requestManager.load(NULL).apply(errorOf(drawable)).into(target);\n\n    verify(target).onLoadFailed(eq(drawable));\n  }\n\n  @Test\n  public void testNullModelPrefersErrorDrawable() {\n    Drawable placeholder = new ColorDrawable(Color.GREEN);\n    Drawable error = new ColorDrawable(Color.RED);\n\n    requestManager.load(NULL).apply(placeholderOf(placeholder).error(error)).into(target);\n\n    verify(target).onLoadFailed(eq(error));\n  }\n\n  @Test\n  public void testLoadBitmap_asBitmap() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    requestManager.asBitmap().load(bitmap).into(target);\n\n    verify(target).onResourceReady(eq(bitmap), any(Transition.class));\n  }\n\n  @Test\n  public void testLoadBitmap_asDrawable() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    requestManager.load(bitmap).into(target);\n\n    ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);\n    verify(target).onResourceReady(captor.capture(), any(Transition.class));\n    BitmapDrawable drawable = (BitmapDrawable) captor.getValue();\n    assertThat(drawable.getBitmap()).isEqualTo(bitmap);\n  }\n\n  @Test\n  public void testLoadDrawable() {\n    Drawable drawable = new ColorDrawable(Color.RED);\n    requestManager.load(drawable).into(target);\n\n    ArgumentCaptor<Drawable> drawableCaptor = ArgumentCaptor.forClass(Drawable.class);\n    verify(target).onResourceReady(drawableCaptor.capture(), any(Transition.class));\n    assertThat(((ColorDrawable) drawableCaptor.getValue()).getColor()).isEqualTo(Color.RED);\n  }\n\n  @Test\n  public void testNullModelPrefersFallbackDrawable() {\n    Drawable placeholder = new ColorDrawable(Color.GREEN);\n    Drawable error = new ColorDrawable(Color.RED);\n    Drawable fallback = new ColorDrawable(Color.BLUE);\n\n    requestManager\n        .load(NULL)\n        .apply(placeholderOf(placeholder).error(error).fallback(fallback))\n        .into(target);\n\n    verify(target).onLoadFailed(eq(fallback));\n  }\n\n  @Test\n  public void testNullModelResolvesToUsePlaceholder() {\n    Drawable placeholder = new ColorDrawable(Color.GREEN);\n\n    requestManager.load(NULL).apply(placeholderOf(placeholder)).into(target);\n\n    verify(target).onLoadFailed(eq(placeholder));\n  }\n\n  @Test\n  public void testByteData() {\n    byte[] data = new byte[] {1, 2, 3, 4, 5, 6};\n    requestManager.load(data).into(target);\n  }\n\n  @Test\n  public void removeFromManagers_afterRequestManagerRemoved_clearsRequest() {\n    target =\n        requestManager\n            .load(mockUri(\"content://uri\"))\n            .into(\n                new CustomTarget<Drawable>() {\n                  @Override\n                  public void onResourceReady(\n                      @NonNull Drawable resource,\n                      @Nullable Transition<? super Drawable> transition) {\n                    // Do nothing.\n                  }\n\n                  @Override\n                  public void onLoadCleared(@Nullable Drawable placeholder) {\n                    // Do nothing, we don't retain a reference to our resource.\n                  }\n                });\n\n    requestManager.onDestroy();\n    requestManager.clear(target);\n\n    assertThat(target.getRequest()).isNull();\n  }\n\n  @Test\n  public void testClone() {\n    Target<Drawable> firstTarget = mock(Target.class);\n    doAnswer(new CallSizeReady(100, 100)).when(firstTarget).getSize(isA(SizeReadyCallback.class));\n    Target<Drawable> secondTarget = mock(Target.class);\n    doAnswer(new CallSizeReady(100, 100)).when(secondTarget).getSize(isA(SizeReadyCallback.class));\n    RequestBuilder<Drawable> firstRequest = requestManager.load(mockUri(\"content://first\"));\n\n    firstRequest.into(firstTarget);\n\n    firstRequest.clone().apply(placeholderOf(new ColorDrawable(Color.RED))).into(secondTarget);\n\n    verify(firstTarget).onResourceReady(isA(Drawable.class), isA(Transition.class));\n    verify(secondTarget)\n        .onResourceReady(ArgumentMatchers.<Drawable>notNull(), isA(Transition.class));\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private <T, Z> void registerFailFactory(Class<T> failModel, Class<Z> failResource) {\n    DataFetcher<Z> failFetcher = mock(DataFetcher.class);\n    doAnswer(new Util.CallDataReady<>(null))\n        .when(failFetcher)\n        .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class));\n    when(failFetcher.getDataClass()).thenReturn(failResource);\n    ModelLoader<T, Z> failLoader = mock(ModelLoader.class);\n    when(failLoader.buildLoadData(isA(failModel), anyInt(), anyInt(), isA(Options.class)))\n        .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), failFetcher));\n    when(failLoader.handles(isA(failModel))).thenReturn(true);\n    ModelLoaderFactory<T, Z> failFactory = mock(ModelLoaderFactory.class);\n    when(failFactory.build(isA(MultiModelLoaderFactory.class))).thenReturn(failLoader);\n\n    Glide.get(context).getRegistry().prepend(failModel, failResource, failFactory);\n  }\n\n  private String mockUri(String uriString) {\n    return mockUri(Uri.parse(uriString), null);\n  }\n\n  private void mockUri(Uri uri) {\n    mockUri(uri, null);\n  }\n\n  private String mockUri(Uri uri, InputStream is) {\n    if (is == null) {\n      is = new ByteArrayInputStream(new byte[0]);\n    }\n    ContentResolver contentResolver = context.getContentResolver();\n    ShadowFileDescriptorContentResolver shadowContentResolver = Shadow.extract(contentResolver);\n    shadowContentResolver.registerInputStream(uri, is);\n\n    AssetFileDescriptor assetFileDescriptor = mock(AssetFileDescriptor.class);\n    ParcelFileDescriptor parcelFileDescriptor = mock(ParcelFileDescriptor.class);\n    when(assetFileDescriptor.getParcelFileDescriptor()).thenReturn(parcelFileDescriptor);\n\n    shadowContentResolver.registerAssetFileDescriptor(uri, assetFileDescriptor);\n    return uri.toString();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private <T> void registerMockStreamModelLoader(final Class<T> modelClass) {\n    ModelLoader<T, InputStream> modelLoader = mockStreamModelLoader(modelClass);\n    ModelLoaderFactory<T, InputStream> modelLoaderFactory = mock(ModelLoaderFactory.class);\n    when(modelLoaderFactory.build(isA(MultiModelLoaderFactory.class))).thenReturn(modelLoader);\n\n    Glide.get(context).getRegistry().prepend(modelClass, InputStream.class, modelLoaderFactory);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private <T> ModelLoader<T, InputStream> mockStreamModelLoader(final Class<T> modelClass) {\n    ModelLoader<T, InputStream> modelLoader = mock(ModelLoader.class);\n    DataFetcher<InputStream> fetcher = mock(DataFetcher.class);\n    try {\n      doAnswer(new Util.CallDataReady<>(new ByteArrayInputStream(new byte[0])))\n          .when(fetcher)\n          .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class));\n    } catch (Exception e) {\n      // Do nothing.\n    }\n    when(fetcher.getDataClass()).thenReturn(InputStream.class);\n    when(modelLoader.buildLoadData(isA(modelClass), anyInt(), anyInt(), isA(Options.class)))\n        .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), fetcher));\n    when(modelLoader.handles(isA(modelClass))).thenReturn(true);\n\n    return modelLoader;\n  }\n\n  private InputStream openGif() {\n    return TestResourceUtil.openResource(getClass(), \"test.gif\");\n  }\n\n  private static class CallSizeReady implements Answer<Void> {\n    private final int width;\n    private final int height;\n\n    CallSizeReady() {\n      this(100, 100);\n    }\n\n    CallSizeReady(int width, int height) {\n      this.width = width;\n      this.height = height;\n    }\n\n    @Override\n    public Void answer(InvocationOnMock invocation) throws Throwable {\n      SizeReadyCallback cb = (SizeReadyCallback) invocation.getArguments()[0];\n      cb.onSizeReady(width, height);\n      return null;\n    }\n  }\n\n  private static <X, Y> void registerMockModelLoader(\n      Class<X> modelClass, Class<Y> dataClass, Y loadedData, Registry registry) {\n    DataFetcher<Y> mockStreamFetcher = mock(DataFetcher.class);\n    when(mockStreamFetcher.getDataClass()).thenReturn(dataClass);\n    try {\n      doAnswer(new Util.CallDataReady<>(loadedData))\n          .when(mockStreamFetcher)\n          .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class));\n    } catch (Exception e) {\n      throw new RuntimeException(e);\n    }\n    ModelLoader<X, Y> mockUrlLoader = mock(ModelLoader.class);\n    when(mockUrlLoader.buildLoadData(isA(modelClass), anyInt(), anyInt(), isA(Options.class)))\n        .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), mockStreamFetcher));\n    when(mockUrlLoader.handles(isA(modelClass))).thenReturn(true);\n    ModelLoaderFactory<X, Y> mockUrlLoaderFactory = mock(ModelLoaderFactory.class);\n    when(mockUrlLoaderFactory.build(isA(MultiModelLoaderFactory.class))).thenReturn(mockUrlLoader);\n\n    registry.replace(modelClass, dataClass, mockUrlLoaderFactory);\n  }\n\n  // TODO: Extending ShadowContentResolver results in exceptions because of some state issues\n  // where we seem to get one content resolver shadow in one part of the test and a different one in\n  // a different part of the test. Each one ends up with different registered uris, which causes\n  // tests to fail. We shouldn't need to do this, but using static maps seems to fix the issue.\n  @Implements(value = ContentResolver.class)\n  @SuppressWarnings(\"unused\")\n  public static class ShadowFileDescriptorContentResolver {\n    private static final Map<Uri, AssetFileDescriptor> URI_TO_FILE_DESCRIPTOR = new HashMap<>();\n    private static final Map<Uri, InputStream> URI_TO_INPUT_STREAMS = new HashMap<>();\n\n    @Resetter\n    public static void reset() {\n      URI_TO_INPUT_STREAMS.clear();\n      URI_TO_FILE_DESCRIPTOR.clear();\n    }\n\n    void registerInputStream(Uri uri, InputStream inputStream) {\n      URI_TO_INPUT_STREAMS.put(uri, inputStream);\n    }\n\n    void registerAssetFileDescriptor(Uri uri, AssetFileDescriptor assetFileDescriptor) {\n      URI_TO_FILE_DESCRIPTOR.put(uri, assetFileDescriptor);\n    }\n\n    @Implementation\n    public InputStream openInputStream(Uri uri) {\n      if (!URI_TO_INPUT_STREAMS.containsKey(uri)) {\n        throw new IllegalArgumentException(\n            \"You must first register an InputStream for uri: \" + uri);\n      }\n      return URI_TO_INPUT_STREAMS.get(uri);\n    }\n\n    @Implementation\n    public AssetFileDescriptor openAssetFileDescriptor(Uri uri, String type) {\n      if (!URI_TO_FILE_DESCRIPTOR.containsKey(uri)) {\n        throw new IllegalArgumentException(\n            \"You must first register an AssetFileDescriptor for \" + \"uri: \" + uri);\n      }\n      return URI_TO_FILE_DESCRIPTOR.get(uri);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/InitializeGlideTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.tests.TearDownGlide;\nimport java.util.Set;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\n\n// This test is about edge cases that might otherwise make debugging more challenging.\n@RunWith(AndroidJUnit4.class)\npublic class InitializeGlideTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  private static final class TestException extends RuntimeException {\n    private static final long serialVersionUID = 2515021766931124927L;\n  }\n\n  @Test\n  public void initialize_whenInternalMethodThrows_throwsException() {\n    assertThrows(\n        TestException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            synchronized (Glide.class) {\n              Glide.checkAndInitializeGlide(\n                  context,\n                  new GeneratedAppGlideModule() {\n                    @NonNull\n                    @Override\n                    Set<Class<?>> getExcludedModuleClasses() {\n                      throw new TestException();\n                    }\n                  });\n            }\n          }\n        });\n  }\n\n  @Test\n  public void initialize_whenInternalMethodThrows_andCalledTwice_throwsException() {\n    GeneratedAppGlideModule throwingGeneratedAppGlideModule =\n        new GeneratedAppGlideModule() {\n          @NonNull\n          @Override\n          Set<Class<?>> getExcludedModuleClasses() {\n            throw new TestException();\n          }\n        };\n    ThrowingRunnable initializeGlide =\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            synchronized (Glide.class) {\n              Glide.checkAndInitializeGlide(context, throwingGeneratedAppGlideModule);\n            }\n          }\n        };\n\n    assertThrows(TestException.class, initializeGlide);\n    // Make sure the second exception isn't hidden by some Glide initialization related exception.\n    assertThrows(TestException.class, initializeGlide);\n  }\n\n  @Test\n  public void isInitialized_whenNotInitialized_returnsFalse() {\n    assertThat(Glide.isInitialized()).isFalse();\n  }\n\n  @Test\n  public void isInitialized_whenInitialized_returnsTrue() {\n    Glide.get(context);\n\n    assertThat(Glide.isInitialized()).isTrue();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/ListPreloaderTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.tests.Util.cast;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.target.Target;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.verification.VerificationMode;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK)\npublic class ListPreloaderTest {\n\n  @Mock private RequestBuilder<Object> request;\n  @Mock private RequestManager requestManager;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n  }\n\n  @Test\n  public void testGetItemsIsCalledIncreasing() {\n    final AtomicBoolean called = new AtomicBoolean(false);\n    final AtomicInteger calledCount = new AtomicInteger();\n\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            called.set(true);\n            final int count = calledCount.getAndIncrement();\n            assertEquals(11 + count, position);\n            return super.getPreloadItems(position);\n          }\n        };\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n    preloader.onScroll(null, 1, 10, 30);\n    assertEquals(10, calledCount.get());\n  }\n\n  @Test\n  public void testGetItemsIsCalledInOrderIncreasing() {\n    final int toPreload = 10;\n    final List<Object> objects = new ArrayList<>();\n    for (int i = 0; i < toPreload; i++) {\n      objects.add(i);\n    }\n\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          private int expectedPosition;\n\n          @Override\n          public int[] getPreloadSize(@NonNull Object item, int adapterPosition, int itemPosition) {\n            return new int[] {10, 10};\n          }\n\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            return objects.subList(position - 11, position + 1 - 11);\n          }\n\n          @Nullable\n          @Override\n          @SuppressWarnings(\"unchecked\")\n          public RequestBuilder<Object> getPreloadRequestBuilder(@NonNull Object item) {\n            assertEquals(objects.get(expectedPosition), item);\n            expectedPosition++;\n            return mock(RequestBuilder.class);\n          }\n        };\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, toPreload);\n    preloader.onScroll(null, 1, 10, 20);\n  }\n\n  @Test\n  public void testGetItemsIsCalledDecreasing() {\n    final AtomicBoolean called = new AtomicBoolean(false);\n    final AtomicInteger calledCount = new AtomicInteger();\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            // Ignore the preload caused from us starting at the end\n            if (position >= 40) {\n              return Collections.emptyList();\n            }\n            final int count = calledCount.getAndIncrement();\n            called.set(true);\n            assertEquals(28 - count, position);\n            return super.getPreloadItems(position);\n          }\n        };\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n    preloader.onScroll(null, 30, 10, 40);\n    preloader.onScroll(null, 29, 10, 40);\n    assertTrue(called.get());\n  }\n\n  @Test\n  public void testGetItemsIsCalledInOrderDecreasing() {\n    final int toPreload = 10;\n    final List<Object> objects = new ArrayList<>();\n    for (int i = 0; i < toPreload; i++) {\n      objects.add(new Object());\n    }\n\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          private int expectedPosition = toPreload - 1;\n\n          @Override\n          public int[] getPreloadSize(@NonNull Object item, int adapterPosition, int itemPosition) {\n            return new int[] {10, 10};\n          }\n\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            if (position == 40) {\n              return Collections.emptyList();\n            }\n            return objects.subList(position, position + 1);\n          }\n\n          @Nullable\n          @Override\n          @SuppressWarnings(\"unchecked\")\n          public RequestBuilder<Object> getPreloadRequestBuilder(@NonNull Object item) {\n            assertEquals(objects.get(expectedPosition), item);\n            expectedPosition--;\n            return mock(RequestBuilder.class);\n          }\n        };\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, toPreload);\n    preloader.onScroll(null, 30, 10, 10);\n    preloader.onScroll(null, 29, 10, 10);\n  }\n\n  @Test\n  public void testGetItemsIsNeverCalledWithEndGreaterThanTotalItems() {\n    final AtomicBoolean called = new AtomicBoolean(false);\n    final AtomicInteger calledCount = new AtomicInteger();\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            called.set(true);\n            final int count = calledCount.getAndIncrement();\n            assertEquals(26 + count, position);\n            return super.getPreloadItems(position);\n          }\n        };\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n    preloader.onScroll(null, 16, 10, 30);\n    assertTrue(called.get());\n  }\n\n  @Test\n  public void testGetItemsIsNeverCalledWithStartLessThanZero() {\n    final AtomicBoolean called = new AtomicBoolean(false);\n    final AtomicInteger calledCount = new AtomicInteger();\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            if (position >= 17) {\n              return Collections.emptyList();\n            }\n            called.set(true);\n            final int count = calledCount.getAndIncrement();\n            assertEquals(5 - count, position);\n            return super.getPreloadItems(position);\n          }\n        };\n\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n    preloader.onScroll(null, 7, 10, 30);\n    preloader.onScroll(null, 6, 10, 30);\n    assertTrue(called.get());\n  }\n\n  @Test\n  public void testDontPreloadItemsRepeatedlyWhileIncreasing() {\n    final AtomicInteger called = new AtomicInteger();\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            final int current = called.getAndIncrement();\n            assertEquals(11 + current, position);\n            return super.getPreloadItems(position);\n          }\n        };\n\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n    preloader.onScroll(null, 1, 10, 30);\n    preloader.onScroll(null, 4, 10, 30);\n\n    assertEquals(13, called.get());\n  }\n\n  @Test\n  public void testDontPreloadItemsRepeatedlyWhileDecreasing() {\n    final AtomicInteger called = new AtomicInteger();\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            if (position >= 20) {\n              return Collections.emptyList();\n            }\n            final int current = called.getAndIncrement();\n            assertEquals(19 - current, position);\n            return super.getPreloadItems(position);\n          }\n        };\n\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n    preloader.onScroll(null, 21, 10, 30);\n    preloader.onScroll(null, 20, 10, 30);\n    preloader.onScroll(null, 17, 10, 30);\n    assertEquals(13, called.get());\n  }\n\n  @Test\n  public void testMultipleItemsForPositionIncreasing() {\n    final List<Object> objects = new ArrayList<>();\n    objects.add(new Object());\n    objects.add(new Object());\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          private int expectedPosition = (1 + 10) * 2;\n\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            return objects;\n          }\n\n          @Override\n          public int[] getPreloadSize(@NonNull Object item, int adapterPosition, int itemPosition) {\n            assertEquals(expectedPosition / 2, adapterPosition);\n            assertEquals(expectedPosition % 2, itemPosition);\n            expectedPosition++;\n            return itemPosition == 0 ? new int[] {10, 11} : new int[] {20, 21};\n          }\n\n          @Nullable\n          @Override\n          public RequestBuilder<Object> getPreloadRequestBuilder(@NonNull Object item) {\n            return request;\n          }\n        };\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n    Iterable<Integer> expected = Arrays.asList(10, 11, 20, 21, 10, 11, 20, 21);\n\n    preloader.onScroll(null, 1, 10, 1 + 10 + 2);\n\n    List<Integer> allValues = getTargetsSizes(request, times(4));\n    assertEquals(expected, allValues);\n  }\n\n  @Test\n  public void testMultipleItemsForPositionDecreasing() {\n    final List<Object> objects = new ArrayList<>();\n    objects.add(new Object());\n    objects.add(new Object());\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          private int expectedPosition = objects.size() * 2 - 1;\n\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            return objects;\n          }\n\n          @Override\n          public int[] getPreloadSize(@NonNull Object item, int adapterPosition, int itemPosition) {\n            assertEquals(expectedPosition / 2, adapterPosition);\n            assertEquals(expectedPosition % 2, itemPosition);\n            expectedPosition--;\n            return itemPosition == 0 ? new int[] {10, 11} : new int[] {20, 21};\n          }\n\n          @Nullable\n          @Override\n          public RequestBuilder<Object> getPreloadRequestBuilder(@NonNull Object item) {\n            return request;\n          }\n        };\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n    Iterable<Integer> expected = Arrays.asList(20, 21, 10, 11, 20, 21, 10, 11);\n\n    preloader.onScroll(null, 3, 2, 3 + 2);\n    preloader.onScroll(null, 2, 2, 3 + 2);\n\n    List<Integer> allValues = getTargetsSizes(request, times(4));\n    assertEquals(expected, allValues);\n  }\n\n  private <Resource> List<Integer> getTargetsSizes(\n      RequestBuilder<Resource> requestBuilder, VerificationMode mode) {\n    ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class);\n    ArgumentCaptor<Target<Resource>> targetArgumentCaptor =\n        cast(ArgumentCaptor.forClass(Target.class));\n    SizeReadyCallback cb = mock(SizeReadyCallback.class);\n    verify(requestBuilder, mode).into(targetArgumentCaptor.capture());\n    for (Target<Resource> target : targetArgumentCaptor.getAllValues()) {\n      target.getSize(cb);\n    }\n    verify(cb, mode).onSizeReady(integerArgumentCaptor.capture(), integerArgumentCaptor.capture());\n    return integerArgumentCaptor.getAllValues();\n  }\n\n  // It's safe to ignore the return value of containsAllIn.\n  @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n  @Test\n  public void testItemsArePreloadedWithGlide() {\n    final List<Object> objects = new ArrayList<>();\n    objects.add(new Object());\n    objects.add(new Object());\n    final HashSet<Object> loadedObjects = new HashSet<>();\n    ListPreloaderAdapter preloaderAdapter =\n        new ListPreloaderAdapter() {\n          @NonNull\n          @Override\n          public List<Object> getPreloadItems(int position) {\n            return objects.subList(position - 11, position - 10);\n          }\n\n          @Nullable\n          @Override\n          public RequestBuilder<Object> getPreloadRequestBuilder(@NonNull Object item) {\n            loadedObjects.add(item);\n            return super.getPreloadRequestBuilder(item);\n          }\n        };\n    ListPreloader<Object> preloader =\n        new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10);\n\n    preloader.onScroll(null, 1, 10, 13);\n    assertThat(loadedObjects).containsAtLeastElementsIn(objects);\n  }\n\n  private static class ListPreloaderAdapter\n      implements ListPreloader.PreloadModelProvider<Object>,\n          ListPreloader.PreloadSizeProvider<Object> {\n\n    public ListPreloaderAdapter() {}\n\n    @NonNull\n    @Override\n    public List<Object> getPreloadItems(int position) {\n      ArrayList<Object> result = new ArrayList<>(1);\n      Collections.fill(result, new Object());\n      return result;\n    }\n\n    @Nullable\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public RequestBuilder<Object> getPreloadRequestBuilder(@NonNull Object item) {\n      return mock(RequestBuilder.class);\n    }\n\n    @Nullable\n    @Override\n    public int[] getPreloadSize(@NonNull Object item, int adapterPosition, int itemPosition) {\n      return new int[] {100, 100};\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/RegistryFactoryTest.java",
    "content": "package com.bumptech.glide;\n\nimport static org.junit.Assert.assertThrows;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.tests.TearDownGlide;\nimport com.bumptech.glide.util.GlideSuppliers.GlideSupplier;\nimport com.google.common.collect.ImmutableList;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class RegistryFactoryTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n  private final Context context = ApplicationProvider.getApplicationContext();\n\n  private static final class TestException extends RuntimeException {\n    private static final long serialVersionUID = 2334956185897161236L;\n  }\n\n  @Test\n  public void create_whenCalledTwiceWithThrowingModule_throwsOriginalException() {\n    AppGlideModule throwingAppGlideModule =\n        new AppGlideModule() {\n          @Override\n          public void registerComponents(\n              @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n            throw new TestException();\n          }\n        };\n\n    Glide glide = Glide.get(context);\n    GlideSupplier<Registry> registrySupplier =\n        RegistryFactory.lazilyCreateAndInitializeRegistry(\n            glide, /* manifestModules= */ ImmutableList.of(), throwingAppGlideModule);\n\n    assertThrows(\n        TestException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            registrySupplier.get();\n          }\n        });\n\n    assertThrows(\n        TestException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() {\n            registrySupplier.get();\n          }\n        });\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/RegistryTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.resource.transcode.ResourceTranscoder;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class RegistryTest {\n\n  @Mock private ModelLoaderFactory<Model, Data> modelLoaderFactory;\n  @Mock private ResourceDecoder<Data, ResourceOne> resourceOneDecoder;\n  @Mock private ResourceDecoder<Data, ResourceTwo> resourceTwoDecoder;\n  @Mock private ResourceTranscoder<ResourceOne, TranscodeOne> resourceOneTranscodeOneTranscoder;\n  private Registry registry;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    registry = new Registry();\n  }\n\n  @Test\n  public void getRegisteredResourceClasses_withNoResources_isEmpty() {\n    assertThat(getRegisteredResourceClasses()).isEmpty();\n  }\n\n  @Test\n  public void getRegisteredResourceClasses_withOneDataClass_noResourceClasses_isEmpty() {\n    registry.append(Model.class, Data.class, modelLoaderFactory);\n    assertThat(getRegisteredResourceClasses()).isEmpty();\n  }\n\n  @Test\n  public void getRegisteredResourceClasses_withOneDataAndResourceClass_noTranscodeClass_isEmpty() {\n    registry.append(Model.class, Data.class, modelLoaderFactory);\n    registry.append(Data.class, ResourceOne.class, resourceOneDecoder);\n    assertThat(getRegisteredResourceClasses()).isEmpty();\n  }\n\n  @Test\n  public void getRegisteredResourceClasses_withOneDataAndResourceAndTranscodeClass_isNotEmpty() {\n    registry.append(Model.class, Data.class, modelLoaderFactory);\n    registry.append(Data.class, ResourceOne.class, resourceOneDecoder);\n    registry.register(ResourceOne.class, TranscodeOne.class, resourceOneTranscodeOneTranscoder);\n    assertThat(getRegisteredResourceClasses()).containsExactly(ResourceOne.class);\n  }\n\n  @Test\n  public void getRegisteredResourceClasses_withMissingTranscodeForOneOfTwoResources_isNotEmpty() {\n    // The loop allows us to make sure that the order in which we call getRegisteredResourceClasses\n    // doesn't affect the output.\n    for (int i = 0; i < 2; i++) {\n      Registry registry = new Registry();\n      registry.append(Model.class, Data.class, modelLoaderFactory);\n\n      registry.append(Data.class, ResourceOne.class, resourceOneDecoder);\n      registry.append(Data.class, ResourceTwo.class, resourceTwoDecoder);\n\n      registry.register(ResourceOne.class, TranscodeOne.class, resourceOneTranscodeOneTranscoder);\n\n      List<Class<?>> resourceOneClasses;\n      List<Class<?>> resourceTwoClasses;\n      if (i == 0) {\n        resourceOneClasses =\n            registry.getRegisteredResourceClasses(\n                Model.class, ResourceOne.class, TranscodeOne.class);\n        resourceTwoClasses =\n            registry.getRegisteredResourceClasses(\n                Model.class, ResourceTwo.class, TranscodeOne.class);\n      } else {\n        resourceTwoClasses =\n            registry.getRegisteredResourceClasses(\n                Model.class, ResourceTwo.class, TranscodeOne.class);\n        resourceOneClasses =\n            registry.getRegisteredResourceClasses(\n                Model.class, ResourceOne.class, TranscodeOne.class);\n      }\n      // ResourceOne has a corresponding transcode class, so we should return it.\n      assertThat(resourceOneClasses).containsExactly(ResourceOne.class);\n      // ResourceTwo has no matching transcode class, so we shouldn't return it.\n      assertThat(resourceTwoClasses).isEmpty();\n    }\n  }\n\n  @Test\n  public void getRegisteredResourceClasses_withOneOfTwoMissingTranscoders_isNotEmpty() {\n    // The loop allows us to make sure that the order in which we call getRegisteredResourceClasses\n    // doesn't affect the output.\n    for (int i = 0; i < 2; i++) {\n      Registry registry = new Registry();\n      registry.append(Model.class, Data.class, modelLoaderFactory);\n\n      registry.append(Data.class, ResourceOne.class, resourceOneDecoder);\n\n      registry.register(ResourceOne.class, TranscodeOne.class, resourceOneTranscodeOneTranscoder);\n\n      List<Class<?>> transcodeOneClasses;\n      List<Class<?>> transcodeTwoClasses;\n      if (i == 0) {\n        transcodeOneClasses =\n            registry.getRegisteredResourceClasses(\n                Model.class, ResourceOne.class, TranscodeOne.class);\n        transcodeTwoClasses =\n            registry.getRegisteredResourceClasses(\n                Model.class, ResourceOne.class, TranscodeTwo.class);\n      } else {\n        transcodeTwoClasses =\n            registry.getRegisteredResourceClasses(\n                Model.class, ResourceOne.class, TranscodeTwo.class);\n        transcodeOneClasses =\n            registry.getRegisteredResourceClasses(\n                Model.class, ResourceOne.class, TranscodeOne.class);\n      }\n      // TranscodeOne has a corresponding ResourceTranscoder, so we expect to see the resource\n      // class.\n      assertThat(transcodeOneClasses).containsExactly(ResourceOne.class);\n      // TranscodeTwo has no corresponding ResourceTranscoder class, so we shouldn't return the\n      // resource class.\n      assertThat(transcodeTwoClasses).isEmpty();\n    }\n  }\n\n  private List<Class<?>> getRegisteredResourceClasses() {\n    return registry.getRegisteredResourceClasses(\n        Model.class, ResourceOne.class, TranscodeOne.class);\n  }\n\n  private static final class Model {\n    // Empty class to represent model classes for readability.\n  }\n\n  private static final class Data {\n    // Empty class to represent data classes for readability.\n  }\n\n  private static final class ResourceOne {\n    // Empty class to represent resource classes for readability.\n  }\n\n  private static final class ResourceTwo {\n    // Empty class to represent another resource class for readability.\n  }\n\n  private static final class TranscodeOne {\n    // Empty class to represent transcode classes for readability.\n  }\n\n  private static final class TranscodeTwo {\n    // Empty class to represent transcode classes for readability.\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/RequestBuilderTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.tests.BackgroundUtil.testInBackground;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.net.Uri;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.resource.SimpleResource;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.SingleRequest;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.target.ViewTarget;\nimport com.bumptech.glide.tests.BackgroundUtil.BackgroundTester;\nimport com.bumptech.glide.tests.TearDownGlide;\nimport com.google.common.testing.EqualsTester;\nimport java.util.concurrent.Executors;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@SuppressWarnings(\"unchecked\")\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK)\npublic class RequestBuilderTest {\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n\n  @Mock private RequestListener<Object> listener1;\n  @Mock private RequestListener<Object> listener2;\n  @Mock private Target<Object> target;\n  @Mock private GlideContext glideContext;\n  @Mock private RequestManager requestManager;\n  @Captor private ArgumentCaptor<SingleRequest<Object>> requestCaptor;\n  private Glide glide;\n  private Application context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    glide = Glide.get(ApplicationProvider.getApplicationContext());\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfContextIsNull() {\n    new RequestBuilder<>(null /*context*/, requestManager, Object.class, context);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsWhenTransitionsOptionsIsNull() {\n    //noinspection ConstantConditions testing if @NonNull is enforced\n    getNullModelRequest().transition(null);\n  }\n\n  @Test\n  public void testDoesNotThrowWithNullModelWhenRequestIsBuilt() {\n    getNullModelRequest().into(target);\n  }\n\n  @Test\n  public void testDoesNotThrowWithNullModelWhenRequestIsBuiltFront() {\n    getNullModelRequest().experimentalIntoFront(target);\n  }\n\n  @Test\n  public void testAddsNewRequestToRequestTracker() {\n    getNullModelRequest().into(target);\n\n    verify(requestManager).track(eq(target), isA(Request.class));\n  }\n\n  @Test\n  public void testAddsNewRequestToRequestTrackerWithCustomExecutor() {\n    getNullModelRequest()\n        .into(target, /* targetListener= */ null, Executors.newSingleThreadExecutor());\n\n    verify(requestManager).track(eq(target), isA(Request.class));\n  }\n\n  @Test\n  public void testAddsNewRequestToRequestTrackerFront() {\n    getNullModelRequest().experimentalIntoFront(target);\n\n    verify(requestManager).track(eq(target), isA(Request.class));\n  }\n\n  @Test\n  public void testRemovesPreviousRequestFromRequestTracker() {\n    Request previous = mock(Request.class);\n    when(target.getRequest()).thenReturn(previous);\n\n    getNullModelRequest().into(target);\n\n    verify(requestManager).clear(eq(target));\n  }\n\n  @Test\n  public void testRemovesPreviousRequestFromRequestTrackerFront() {\n    Request previous = mock(Request.class);\n    when(target.getRequest()).thenReturn(previous);\n\n    getNullModelRequest().experimentalIntoFront(target);\n\n    verify(requestManager).clear(eq(target));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenNullTarget() {\n    //noinspection ConstantConditions testing if @NonNull is enforced\n    getNullModelRequest().into((Target<Object>) null);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenNullTargetFront() {\n    //noinspection ConstantConditions testing if @NonNull is enforced\n    getNullModelRequest().experimentalIntoFront((Target<Object>) null);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenNullView() {\n    getNullModelRequest().into((ImageView) null);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenNullViewFront() {\n    getNullModelRequest().experimentalIntoFront((ImageView) null);\n  }\n\n  @Test(expected = RuntimeException.class)\n  public void testThrowsIfIntoViewCalledOnBackgroundThread() throws InterruptedException {\n    final ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext());\n    testInBackground(\n        new BackgroundTester() {\n          @Override\n          public void runTest() {\n            getNullModelRequest().into(imageView);\n          }\n        });\n  }\n\n  @Test(expected = RuntimeException.class)\n  public void testThrowsIfIntoViewCalledOnBackgroundThreadFront() throws InterruptedException {\n    final ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext());\n    testInBackground(\n        new BackgroundTester() {\n          @Override\n          public void runTest() {\n            getNullModelRequest().experimentalIntoFront(imageView);\n          }\n        });\n  }\n\n  @Test\n  public void doesNotThrowIfIntoTargetCalledOnBackgroundThread() throws InterruptedException {\n    final Target<Object> target = mock(Target.class);\n    testInBackground(\n        new BackgroundTester() {\n          @Override\n          public void runTest() {\n            getNullModelRequest().into(target);\n          }\n        });\n  }\n\n  @Test\n  public void doesNotThrowIfIntoTargetCalledOnBackgroundThreadFront() throws InterruptedException {\n    final Target<Object> target = mock(Target.class);\n    testInBackground(\n        new BackgroundTester() {\n          @Override\n          public void runTest() {\n            getNullModelRequest().experimentalIntoFront(target);\n          }\n        });\n  }\n\n  @Test\n  public void doesNotThrowIfIntoTargetWithCustomExecutorCalledOnBackgroundThread()\n      throws InterruptedException {\n    final Target<Object> target = mock(Target.class);\n    testInBackground(\n        new BackgroundTester() {\n          @Override\n          public void runTest() {\n            getNullModelRequest()\n                .into(target, /* targetListener= */ null, Executors.newSingleThreadExecutor());\n          }\n        });\n  }\n\n  @Test\n  public void testMultipleRequestListeners() {\n    getNullModelRequest().addListener(listener1).addListener(listener2).into(target);\n    verify(requestManager).track(any(Target.class), requestCaptor.capture());\n    requestCaptor\n        .getValue()\n        .onResourceReady(\n            new SimpleResource<>(new Object()),\n            DataSource.LOCAL,\n            /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());\n    verify(listener2)\n        .onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());\n  }\n\n  @Test\n  public void testMultipleRequestListenersFront() {\n    getNullModelRequest()\n        .addListener(listener1)\n        .addListener(listener2)\n        .experimentalIntoFront(target);\n    verify(requestManager).track(any(Target.class), requestCaptor.capture());\n    requestCaptor\n        .getValue()\n        .onResourceReady(\n            new SimpleResource<>(new Object()),\n            DataSource.LOCAL,\n            /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());\n    verify(listener2)\n        .onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());\n  }\n\n  @Test\n  public void testListenerApiOverridesListeners() {\n    getNullModelRequest().addListener(listener1).listener(listener2).into(target);\n    verify(requestManager).track(any(Target.class), requestCaptor.capture());\n    requestCaptor\n        .getValue()\n        .onResourceReady(\n            new SimpleResource<>(new Object()),\n            DataSource.LOCAL,\n            /* isLoadedFromAlternateCacheKey= */ false);\n\n    // The #listener API removes any previous listeners, so the first listener should not be called.\n    verify(listener1, never())\n        .onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());\n    verify(listener2)\n        .onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());\n  }\n\n  @Test\n  public void testListenerApiOverridesListenersFront() {\n    getNullModelRequest().addListener(listener1).listener(listener2).experimentalIntoFront(target);\n    verify(requestManager).track(any(Target.class), requestCaptor.capture());\n    requestCaptor\n        .getValue()\n        .onResourceReady(\n            new SimpleResource<>(new Object()),\n            DataSource.LOCAL,\n            /* isLoadedFromAlternateCacheKey= */ false);\n\n    // The #listener API removes any previous listeners, so the first listener should not be called.\n    verify(listener1, never())\n        .onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());\n    verify(listener2)\n        .onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());\n  }\n\n  @Test\n  public void testEquals() {\n    Object firstModel = new Object();\n    Object secondModel = new Object();\n\n    RequestListener<Object> firstListener =\n        new RequestListener<>() {\n          @Override\n          public boolean onLoadFailed(\n              @Nullable GlideException e,\n              Object model,\n              @NonNull Target<Object> target,\n              boolean isFirstResource) {\n            return false;\n          }\n\n          @Override\n          public boolean onResourceReady(\n              @NonNull Object resource,\n              @NonNull Object model,\n              Target<Object> target,\n              @NonNull DataSource dataSource,\n              boolean isFirstResource) {\n            return false;\n          }\n        };\n    RequestListener<Object> secondListener =\n        new RequestListener<>() {\n          @Override\n          public boolean onLoadFailed(\n              @Nullable GlideException e,\n              Object model,\n              @NonNull Target<Object> target,\n              boolean isFirstResource) {\n            return false;\n          }\n\n          @Override\n          public boolean onResourceReady(\n              @NonNull Object resource,\n              @NonNull Object model,\n              Target<Object> target,\n              @NonNull DataSource dataSource,\n              boolean isFirstResource) {\n            return false;\n          }\n        };\n\n    new EqualsTester()\n        .addEqualityGroup(new Object())\n        .addEqualityGroup(newRequestBuilder(Object.class), newRequestBuilder(Object.class))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).load((Object) null),\n            newRequestBuilder(Object.class).load((Object) null),\n            newRequestBuilder(Object.class).load((Uri) null))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).load(firstModel),\n            newRequestBuilder(Object.class).load(firstModel))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).load(secondModel),\n            newRequestBuilder(Object.class).load(secondModel))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).load(Uri.EMPTY),\n            newRequestBuilder(Object.class).load(Uri.EMPTY))\n        .addEqualityGroup(\n            newRequestBuilder(Uri.class).load(Uri.EMPTY),\n            newRequestBuilder(Uri.class).load(Uri.EMPTY))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).centerCrop(),\n            newRequestBuilder(Object.class).centerCrop())\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).addListener(firstListener),\n            newRequestBuilder(Object.class).addListener(firstListener))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).addListener(secondListener),\n            newRequestBuilder(Object.class).addListener(secondListener))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).error(newRequestBuilder(Object.class)),\n            newRequestBuilder(Object.class).error(newRequestBuilder(Object.class)))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).error(firstModel),\n            newRequestBuilder(Object.class).error(firstModel),\n            newRequestBuilder(Object.class).error(newRequestBuilder(Object.class).load(firstModel)))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class).error(secondModel),\n            newRequestBuilder(Object.class).error(secondModel),\n            newRequestBuilder(Object.class)\n                .error(newRequestBuilder(Object.class).load(secondModel)))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class)\n                .error(newRequestBuilder(Object.class).load(firstModel).centerCrop()),\n            newRequestBuilder(Object.class)\n                .error(newRequestBuilder(Object.class).load(firstModel).centerCrop()))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class)\n                .thumbnail(newRequestBuilder(Object.class).load(firstModel)),\n            newRequestBuilder(Object.class)\n                .thumbnail(newRequestBuilder(Object.class).load(firstModel)))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class)\n                .thumbnail(newRequestBuilder(Object.class).load(secondModel)),\n            newRequestBuilder(Object.class)\n                .thumbnail(newRequestBuilder(Object.class).load(secondModel)))\n        .addEqualityGroup(\n            newRequestBuilder(Object.class)\n                .transition(new GenericTransitionOptions<>().dontTransition()),\n            newRequestBuilder(Object.class)\n                .transition(new GenericTransitionOptions<>().dontTransition()))\n        .testEquals();\n  }\n\n  private RequestBuilder<Object> getNullModelRequest() {\n    return newRequestBuilder(Object.class).load((Object) null);\n  }\n\n  private <ModelT> RequestBuilder<ModelT> newRequestBuilder(Class<ModelT> modelClass) {\n    when(glideContext.buildImageViewTarget(isA(ImageView.class), isA(Class.class)))\n        .thenReturn(mock(ViewTarget.class));\n    when(glideContext.getDefaultRequestOptions()).thenReturn(new RequestOptions());\n    when(requestManager.getDefaultRequestOptions()).thenReturn(new RequestOptions());\n    when(requestManager.getDefaultTransitionOptions(any(Class.class)))\n        .thenReturn(new GenericTransitionOptions<>());\n    return new RequestBuilder<>(glide, requestManager, modelClass, context);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/RequestManagerTest.java",
    "content": "package com.bumptech.glide;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.BackgroundUtil.testInBackground;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.manager.ConnectivityMonitor;\nimport com.bumptech.glide.manager.ConnectivityMonitor.ConnectivityListener;\nimport com.bumptech.glide.manager.ConnectivityMonitorFactory;\nimport com.bumptech.glide.manager.Lifecycle;\nimport com.bumptech.glide.manager.RequestManagerTreeNode;\nimport com.bumptech.glide.manager.RequestTracker;\nimport com.bumptech.glide.request.target.CustomTarget;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.tests.BackgroundUtil;\nimport com.bumptech.glide.tests.TearDownGlide;\nimport java.io.File;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class RequestManagerTest {\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n\n  @Mock private Lifecycle lifecycle = mock(Lifecycle.class);\n  @Mock private RequestManagerTreeNode treeNode = mock(RequestManagerTreeNode.class);\n\n  private RequestManager manager;\n  private ConnectivityMonitor connectivityMonitor;\n  private RequestTracker requestTracker;\n  private ConnectivityListener connectivityListener;\n  private Application context;\n  private CustomTarget<Drawable> target;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n    connectivityMonitor = mock(ConnectivityMonitor.class);\n    ConnectivityMonitorFactory factory = mock(ConnectivityMonitorFactory.class);\n    when(factory.build(isA(Context.class), isA(ConnectivityMonitor.ConnectivityListener.class)))\n        .thenAnswer(\n            new Answer<ConnectivityMonitor>() {\n              @Override\n              public ConnectivityMonitor answer(InvocationOnMock invocation) {\n                connectivityListener = (ConnectivityListener) invocation.getArguments()[1];\n                return connectivityMonitor;\n              }\n            });\n\n    target =\n        new CustomTarget<Drawable>() {\n          @Override\n          public void onResourceReady(\n              @NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {\n            // Empty.\n          }\n\n          @Override\n          public void onLoadCleared(@Nullable Drawable placeholder) {}\n        };\n\n    requestTracker = mock(RequestTracker.class);\n    manager =\n        new RequestManager(\n            Glide.get(ApplicationProvider.getApplicationContext()),\n            lifecycle,\n            treeNode,\n            requestTracker,\n            factory,\n            context);\n  }\n\n  @Test\n  public void testPauseRequestsPausesRequests() {\n    manager.pauseRequests();\n\n    verify(requestTracker).pauseRequests();\n  }\n\n  @Test\n  public void testResumeRequestsResumesRequests() {\n    manager.resumeRequests();\n\n    verify(requestTracker).resumeRequests();\n  }\n\n  @Test\n  public void testPausesRequestsOnStop() {\n    manager.onStart();\n    manager.onStop();\n\n    verify(requestTracker).pauseRequests();\n  }\n\n  @Test\n  public void testResumesRequestsOnStart() {\n    manager.onStart();\n\n    verify(requestTracker).resumeRequests();\n  }\n\n  @Test\n  public void testClearsRequestsOnDestroy() {\n    manager.onDestroy();\n\n    verify(requestTracker).clearRequests();\n  }\n\n  @Test\n  public void testAddsConnectivityMonitorToLifecycleWhenConstructed() {\n    verify(lifecycle).addListener(eq(connectivityMonitor));\n  }\n\n  @Test\n  public void testAddsSelfToLifecycleWhenConstructed() {\n    verify(lifecycle).addListener(eq(manager));\n  }\n\n  @Test\n  public void testRestartsRequestOnConnected() {\n    connectivityListener.onConnectivityChanged(true);\n\n    verify(requestTracker).restartRequests();\n  }\n\n  @Test\n  public void testDoesNotRestartRequestsOnDisconnected() {\n    connectivityListener.onConnectivityChanged(false);\n\n    verify(requestTracker, never()).restartRequests();\n  }\n\n  @Test\n  public void resumeRequests_whenCalledOnBackgroundThread_doesNotThrow()\n      throws InterruptedException {\n    testInBackground(\n        new BackgroundUtil.BackgroundTester() {\n          @Override\n          public void runTest() {\n            manager.resumeRequests();\n          }\n        });\n  }\n\n  @Test\n  public void pauseRequests_whenCalledOnBackgroundThread_doesNotThrow()\n      throws InterruptedException {\n    testInBackground(\n        new BackgroundUtil.BackgroundTester() {\n          @Override\n          public void runTest() {\n            manager.pauseRequests();\n          }\n        });\n  }\n\n  @Test\n  public void testDelegatesIsPausedToRequestTracker() {\n    when(requestTracker.isPaused()).thenReturn(true);\n    assertTrue(manager.isPaused());\n    when(requestTracker.isPaused()).thenReturn(false);\n    assertFalse(manager.isPaused());\n  }\n\n  @Test\n  public void clear_withRequestStartedInSiblingManager_doesNotThrow() {\n    final RequestManager child1 =\n        new RequestManager(\n            Glide.get(context),\n            lifecycle,\n            new RequestManagerTreeNode() {\n              @NonNull\n              @Override\n              public Set<RequestManager> getDescendants() {\n                return Collections.emptySet();\n              }\n            },\n            context);\n    final RequestManager child2 =\n        new RequestManager(\n            Glide.get(context),\n            lifecycle,\n            new RequestManagerTreeNode() {\n              @NonNull\n              @Override\n              public Set<RequestManager> getDescendants() {\n                return Collections.emptySet();\n              }\n            },\n            context);\n    new RequestManager(\n        Glide.get(context),\n        lifecycle,\n        new RequestManagerTreeNode() {\n          @NonNull\n          @Override\n          public Set<RequestManager> getDescendants() {\n            return new HashSet<>(java.util.Arrays.asList(child1, child2));\n          }\n        },\n        context);\n\n    File file = new File(\"fake\");\n    child1.load(file).into(target);\n    child2.clear(target);\n  }\n\n  @Test\n  public void clear_withRequestStartedInChildManager_doesNotThrow() {\n    final RequestManager child =\n        new RequestManager(\n            Glide.get(context),\n            lifecycle,\n            new RequestManagerTreeNode() {\n              @NonNull\n              @Override\n              public Set<RequestManager> getDescendants() {\n                return Collections.emptySet();\n              }\n            },\n            context);\n    RequestManager parent =\n        new RequestManager(\n            Glide.get(context),\n            lifecycle,\n            new RequestManagerTreeNode() {\n              @NonNull\n              @Override\n              public Set<RequestManager> getDescendants() {\n                return Collections.singleton(child);\n              }\n            },\n            context);\n\n    File file = new File(\"fake\");\n    child.load(file).into(target);\n    parent.clear(target);\n  }\n\n  @Test\n  public void clear_withRequestStartedInParentManager_doesNotThrow() {\n    final RequestManager child =\n        new RequestManager(\n            Glide.get(context),\n            lifecycle,\n            new RequestManagerTreeNode() {\n              @NonNull\n              @Override\n              public Set<RequestManager> getDescendants() {\n                return Collections.emptySet();\n              }\n            },\n            context);\n    RequestManager parent =\n        new RequestManager(\n            Glide.get(context),\n            lifecycle,\n            new RequestManagerTreeNode() {\n              @NonNull\n              @Override\n              public Set<RequestManager> getDescendants() {\n                return Collections.singleton(child);\n              }\n            },\n            context);\n\n    File file = new File(\"fake\");\n\n    parent.load(file).into(target);\n    child.clear(target);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/ImageHeaderParserUtilsTest.java",
    "content": "package com.bumptech.glide.load;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assume.assumeTrue;\n\nimport android.content.Context;\nimport android.os.ParcelFileDescriptor;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class ImageHeaderParserUtilsTest {\n  private final List<FakeImageHeaderParser> fakeParsers =\n      Arrays.asList(new FakeImageHeaderParser(), new FakeImageHeaderParser());\n  private List<ImageHeaderParser> parsers;\n  private final Context context = ApplicationProvider.getApplicationContext();\n  private final byte[] expectedData = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8};\n  private final LruArrayPool lruArrayPool = new LruArrayPool();\n\n  @Before\n  public void setUp() {\n    parsers = new ArrayList<ImageHeaderParser>();\n    for (FakeImageHeaderParser parser : fakeParsers) {\n      parsers.add(parser);\n    }\n  }\n\n  @Test\n  public void getType_withTwoParsers_andStream_rewindsBeforeEachParser() throws IOException {\n    ImageHeaderParserUtils.getType(parsers, new ByteArrayInputStream(expectedData), lruArrayPool);\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  @Test\n  public void getType_withTwoParsers_andByteBuffer_rewindsBeforeEachParser() throws IOException {\n    ImageHeaderParserUtils.getType(parsers, ByteBuffer.wrap(expectedData));\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  @Test\n  public void getType_withTwoParsers_andFileDescriptor_rewindsBeforeEachParser()\n      throws IOException {\n    // This test can't work if file descriptor rewinding isn't supported. Sadly that means this\n    // test doesn't work in Robolectric.\n    assumeTrue(ParcelFileDescriptorRewinder.isSupported());\n\n    ParcelFileDescriptor fileDescriptor = null;\n    try {\n      fileDescriptor = asFileDescriptor(expectedData);\n      ParcelFileDescriptorRewinder rewinder = new ParcelFileDescriptorRewinder(fileDescriptor);\n      ImageHeaderParserUtils.getType(parsers, rewinder, lruArrayPool);\n    } finally {\n      if (fileDescriptor != null) {\n        fileDescriptor.close();\n      }\n    }\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  @Test\n  public void getOrientation_withTwoParsers_andStream_rewindsBeforeEachParser() throws IOException {\n    ImageHeaderParserUtils.getOrientation(\n        parsers, new ByteArrayInputStream(expectedData), lruArrayPool);\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  @Test\n  public void getOrientation_withTwoParsers_andByteBuffer_rewindsBeforeEachParser()\n      throws IOException {\n    ImageHeaderParserUtils.getOrientation(parsers, ByteBuffer.wrap(expectedData), lruArrayPool);\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  @Test\n  public void getOrientation_withTwoParsers_andFileDescriptor_rewindsBeforeEachParser()\n      throws IOException {\n    // This test can't work if file descriptor rewinding isn't supported. Sadly that means this\n    // test doesn't work in Robolectric.\n    assumeTrue(ParcelFileDescriptorRewinder.isSupported());\n    ParcelFileDescriptor fileDescriptor = null;\n    try {\n      fileDescriptor = asFileDescriptor(expectedData);\n      ParcelFileDescriptorRewinder rewinder = new ParcelFileDescriptorRewinder(fileDescriptor);\n      ImageHeaderParserUtils.getOrientation(parsers, rewinder, lruArrayPool);\n    } finally {\n      if (fileDescriptor != null) {\n        fileDescriptor.close();\n      }\n    }\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  @Test\n  public void hasJpegMpf_withTwoParsers_andStream_rewindsBeforeEachParser() throws IOException {\n    ImageHeaderParserUtils.hasJpegMpf(\n        parsers, new ByteArrayInputStream(expectedData), lruArrayPool);\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  @Test\n  public void hasJpegMpf_withTwoParsers_andByteBuffer_rewindsBeforeEachParser() throws IOException {\n    ImageHeaderParserUtils.hasJpegMpf(parsers, ByteBuffer.wrap(expectedData), lruArrayPool);\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  @Test\n  public void hasJpegMpf_withTwoParsers_andFileDescriptor_rewindsBeforeEachParser()\n      throws IOException {\n    // This test can't work if file descriptor rewinding isn't supported. Sadly that means this\n    // test doesn't work in Robolectric.\n    assumeTrue(ParcelFileDescriptorRewinder.isSupported());\n    ParcelFileDescriptor fileDescriptor = null;\n    try {\n      fileDescriptor = asFileDescriptor(expectedData);\n      ParcelFileDescriptorRewinder rewinder = new ParcelFileDescriptorRewinder(fileDescriptor);\n      ImageHeaderParserUtils.hasJpegMpf(parsers, rewinder, lruArrayPool);\n    } finally {\n      if (fileDescriptor != null) {\n        fileDescriptor.close();\n      }\n    }\n\n    assertAllParsersReceivedTheSameData();\n  }\n\n  private void assertAllParsersReceivedTheSameData() {\n    for (FakeImageHeaderParser parser : fakeParsers) {\n      assertThat(parser.data).isNotNull();\n      assertThat(parser.data).asList().containsExactlyElementsIn(asList(expectedData)).inOrder();\n    }\n  }\n\n  private static List<Byte> asList(byte[] data) {\n    List<Byte> result = new ArrayList<>();\n    for (byte item : data) {\n      result.add(item);\n    }\n    return result;\n  }\n\n  private ParcelFileDescriptor asFileDescriptor(byte[] data) throws IOException {\n    File file = new File(context.getCacheDir(), \"temp\");\n    OutputStream os = null;\n    try {\n      os = new FileOutputStream(file);\n      os.write(data);\n      os.close();\n    } finally {\n      if (os != null) {\n        os.close();\n      }\n    }\n    return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);\n  }\n\n  private static final class FakeImageHeaderParser implements ImageHeaderParser {\n\n    private byte[] data;\n\n    private void readData(InputStream is) throws IOException {\n      readData(ByteBufferUtil.fromStream(is));\n    }\n\n    // This is rather roundabout, but it's a simple way of reading the remaining data in the buffer.\n    private void readData(ByteBuffer byteBuffer) {\n\n      byte[] data = new byte[byteBuffer.remaining()];\n      // A 0 length means we read no data. If we try to pass this to ByteBuffer it will throw. We'd\n      // rather not get that obscure exception and instead have an assertion above trigger because\n      // we didn't read enough data. So we work around the exception here if we have no data to\n      // read.\n      if (data.length != 0) {\n        byteBuffer.get(data, byteBuffer.position(), byteBuffer.remaining());\n      }\n      this.data = data;\n    }\n\n    @NonNull\n    @Override\n    public ImageType getType(@NonNull InputStream is) throws IOException {\n      readData(is);\n      return ImageType.UNKNOWN;\n    }\n\n    @NonNull\n    @Override\n    public ImageType getType(@NonNull ByteBuffer byteBuffer) throws IOException {\n      readData(byteBuffer);\n      return ImageType.UNKNOWN;\n    }\n\n    @Override\n    public int getOrientation(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)\n        throws IOException {\n      readData(is);\n      return ImageHeaderParser.UNKNOWN_ORIENTATION;\n    }\n\n    @Override\n    public int getOrientation(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)\n        throws IOException {\n      readData(byteBuffer);\n      return ImageHeaderParser.UNKNOWN_ORIENTATION;\n    }\n\n    @Override\n    public boolean hasJpegMpf(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)\n        throws IOException {\n      readData(is);\n      return false;\n    }\n\n    @Override\n    public boolean hasJpegMpf(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)\n        throws IOException {\n      readData(byteBuffer);\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/MultiTransformationTest.java",
    "content": "package com.bumptech.glide.load;\n\nimport static com.bumptech.glide.tests.Util.anyContext;\nimport static com.bumptech.glide.tests.Util.anyResource;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(AndroidJUnit4.class)\n@SuppressWarnings(\"unchecked\")\npublic class MultiTransformationTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Mock private Transformation<Object> first;\n  @Mock private Transformation<Object> second;\n  @Mock private Resource<Object> initial;\n  @Mock private Resource<Object> firstTransformed;\n  @Mock private Resource<Object> secondTransformed;\n  private Application context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    context = ApplicationProvider.getApplicationContext();\n\n    doAnswer(new Util.WriteDigest(\"first\"))\n        .when(first)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    doAnswer(new Util.WriteDigest(\"second\"))\n        .when(second)\n        .updateDiskCacheKey(any(MessageDigest.class));\n  }\n\n  @Test\n  public void testAppliesTransformationsInOrder() {\n    final int width = 584;\n    final int height = 768;\n\n    MultiTransformation<Object> transformation = new MultiTransformation<>(first, second);\n    when(first.transform(anyContext(), eq(initial), eq(width), eq(height)))\n        .thenReturn(firstTransformed);\n\n    when(second.transform(anyContext(), eq(firstTransformed), eq(width), eq(height)))\n        .thenReturn(secondTransformed);\n\n    assertEquals(secondTransformed, transformation.transform(context, initial, width, height));\n  }\n\n  @Test\n  public void testInitialResourceIsNotRecycled() {\n    when(first.transform(anyContext(), anyResource(), anyInt(), anyInt()))\n        .thenReturn(firstTransformed);\n\n    MultiTransformation<Object> transformation = new MultiTransformation<>(first);\n\n    transformation.transform(context, initial, 123, 456);\n\n    verify(initial, never()).recycle();\n  }\n\n  @Test\n  public void testInitialResourceIsNotRecycledEvenIfReturnedByMultipleTransformations() {\n    when(first.transform(anyContext(), anyResource(), anyInt(), anyInt())).thenReturn(initial);\n    when(second.transform(anyContext(), anyResource(), anyInt(), anyInt())).thenReturn(initial);\n\n    MultiTransformation<Object> transformation = new MultiTransformation<>(first, second);\n    transformation.transform(context, initial, 1111, 2222);\n\n    verify(initial, never()).recycle();\n  }\n\n  @Test\n  public void\n      testInitialResourceIsNotRecycledIfReturnedByOneTransformationButNotByALaterTransformation() {\n    when(first.transform(anyContext(), anyResource(), anyInt(), anyInt())).thenReturn(initial);\n    when(second.transform(anyContext(), anyResource(), anyInt(), anyInt()))\n        .thenReturn(mockResource());\n\n    MultiTransformation<Object> transformation = new MultiTransformation<>(first, second);\n    transformation.transform(context, initial, 1, 2);\n\n    verify(initial, never()).recycle();\n  }\n\n  @Test\n  public void testFinalResourceIsNotRecycled() {\n    when(first.transform(anyContext(), anyResource(), anyInt(), anyInt()))\n        .thenReturn(firstTransformed);\n\n    MultiTransformation<Object> transformation = new MultiTransformation<>(first);\n\n    transformation.transform(context, mockResource(), 111, 222);\n\n    verify(firstTransformed, never()).recycle();\n  }\n\n  @Test\n  public void testIntermediateResourcesAreRecycled() {\n    when(first.transform(anyContext(), anyResource(), anyInt(), anyInt()))\n        .thenReturn(firstTransformed);\n    when(second.transform(anyContext(), anyResource(), anyInt(), anyInt()))\n        .thenReturn(secondTransformed);\n\n    MultiTransformation<Object> transformation = new MultiTransformation<>(first, second);\n\n    transformation.transform(context, mockResource(), 233, 454);\n\n    verify(firstTransformed).recycle();\n  }\n\n  @Test\n  public void testEquals() throws NoSuchAlgorithmException {\n    keyTester\n        .addEquivalenceGroup(new MultiTransformation<>(first), new MultiTransformation<>(first))\n        .addEquivalenceGroup(new MultiTransformation<>(second))\n        .addEquivalenceGroup(new MultiTransformation<>(first, second))\n        .addEquivalenceGroup(new MultiTransformation<>(second, first))\n        .addRegressionTest(\n            new MultiTransformation<>(first),\n            \"a7937b64b8caa58f03721bb6bacf5c78cb235febe0e70b1b84cd99541461a08e\")\n        .addRegressionTest(\n            new MultiTransformation<>(first, second),\n            \"da83f63e1a473003712c18f5afc5a79044221943d1083c7c5a7ac7236d85e8d2\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/OptionsTest.java",
    "content": "package com.bumptech.glide.load;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Option.CacheKeyUpdater;\nimport com.bumptech.glide.tests.KeyTester;\nimport java.nio.ByteBuffer;\nimport java.security.MessageDigest;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class OptionsTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Test\n  public void testEquals() {\n    Option<Object> firstMemoryOption = Option.memory(\"firstKey\");\n    Object firstValue = new Object();\n    Option<Object> secondMemoryOption = Option.memory(\"secondKey\");\n    Object secondValue = new Object();\n\n    CacheKeyUpdater<Integer> updater =\n        new CacheKeyUpdater<Integer>() {\n          @Override\n          public void update(\n              @NonNull byte[] keyBytes,\n              @NonNull Integer value,\n              @NonNull MessageDigest messageDigest) {\n            messageDigest.update(keyBytes);\n            messageDigest.update(ByteBuffer.allocate(4).putInt(value).array());\n          }\n        };\n    Option<Integer> firstDiskOption = Option.disk(\"firstDisk\", updater);\n    Option<Integer> secondDiskOption = Option.disk(\"secondDisk\", updater);\n\n    keyTester\n        .addEquivalenceGroup(new Options(), new Options())\n        .addEquivalenceGroup(\n            new Options().set(firstMemoryOption, firstValue),\n            new Options().set(firstMemoryOption, firstValue))\n        .addEquivalenceGroup(\n            new Options().set(secondMemoryOption, secondValue),\n            new Options().set(secondMemoryOption, secondValue))\n        .addEquivalenceGroup(\n            new Options().set(firstMemoryOption, firstValue).set(secondMemoryOption, secondValue),\n            new Options().set(firstMemoryOption, firstValue).set(secondMemoryOption, secondValue),\n            new Options().set(secondMemoryOption, secondValue).set(firstMemoryOption, firstValue))\n        .addEquivalenceGroup(new Options().set(firstMemoryOption, secondValue))\n        .addEquivalenceGroup(new Options().set(secondMemoryOption, firstValue))\n        .addEquivalenceGroup(\n            new Options().set(firstDiskOption, 1), new Options().set(firstDiskOption, 1))\n        .addEquivalenceGroup(\n            new Options().set(secondDiskOption, 1), new Options().set(secondDiskOption, 1))\n        .addEquivalenceGroup(new Options().set(firstDiskOption, 2))\n        .addEquivalenceGroup(new Options().set(secondDiskOption, 2))\n        .addEquivalenceGroup(\n            new Options().set(firstDiskOption, 1).set(secondDiskOption, 2),\n            new Options().set(secondDiskOption, 2).set(firstDiskOption, 1))\n        .addEmptyDigestRegressionTest(new Options().set(firstMemoryOption, firstValue))\n        .addEmptyDigestRegressionTest(\n            new Options().set(firstMemoryOption, firstValue).set(secondMemoryOption, secondValue))\n        .addRegressionTest(\n            new Options().set(firstDiskOption, 123),\n            \"3c87124d1a765dc3d566f947d536ef140a4aca645c0947f702356714855b4a8e\")\n        .addRegressionTest(\n            new Options().set(firstDiskOption, 123).set(secondDiskOption, 123),\n            \"6697f654686c9a925905db3840e9c99944642c2b91d6200360d77639c1754d51\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/BufferedOutputStreamFuzzTest.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static org.junit.Assert.fail;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Random;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\n/**\n * Runs some tests based on a random seed that asserts the output of writing to our buffered stream\n * matches the output of writing to {@link java.io.ByteArrayOutputStream}.\n */\n@RunWith(JUnit4.class)\npublic class BufferedOutputStreamFuzzTest {\n  private static final int TESTS = 500;\n  private static final int BUFFER_SIZE = 10;\n  private static final int WRITES_PER_TEST = 50;\n  private static final int MAX_BYTES_PER_WRITE = BUFFER_SIZE * 6;\n  private static final Random RANDOM = new Random(-3207167907493985134L);\n\n  @Mock private ArrayPool arrayPool;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    when(arrayPool.get(anyInt(), eq(byte[].class)))\n        .thenAnswer(\n            new Answer<byte[]>() {\n              @Override\n              public byte[] answer(InvocationOnMock invocation) throws Throwable {\n                int size = (Integer) invocation.getArguments()[0];\n                return new byte[size];\n              }\n            });\n  }\n\n  @Test\n  public void runFuzzTest() throws IOException {\n    for (int i = 0; i < TESTS; i++) {\n      runTest(RANDOM);\n    }\n  }\n\n  private void runTest(Random random) throws IOException {\n    List<Write> writes = new ArrayList<>(WRITES_PER_TEST);\n    for (int i = 0; i < WRITES_PER_TEST; i++) {\n      WriteType writeType = getType(random);\n      writes.add(getWrite(random, writeType));\n    }\n\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n\n    ByteArrayOutputStream wrapped = new ByteArrayOutputStream();\n    BufferedOutputStream bufferedOutputStream =\n        new BufferedOutputStream(wrapped, arrayPool, BUFFER_SIZE);\n\n    for (Write write : writes) {\n      switch (write.writeType) {\n        case BYTE:\n          byteArrayOutputStream.write(write.data[0]);\n          bufferedOutputStream.write(write.data[0]);\n          break;\n        case BUFFER:\n          byteArrayOutputStream.write(write.data);\n          bufferedOutputStream.write(write.data);\n          break;\n        case OFFSET_BUFFER:\n          byteArrayOutputStream.write(write.data, write.offset, write.length);\n          bufferedOutputStream.write(write.data, write.offset, write.length);\n          break;\n        default:\n          throw new IllegalArgumentException();\n      }\n    }\n\n    byte[] fromByteArrayStream = byteArrayOutputStream.toByteArray();\n    bufferedOutputStream.close();\n    byte[] fromWrappedStream = wrapped.toByteArray();\n    if (!Arrays.equals(fromWrappedStream, fromByteArrayStream)) {\n      StringBuilder writesBuilder = new StringBuilder();\n      for (Write write : writes) {\n        writesBuilder.append(write).append(\"\\n\");\n      }\n      fail(\n          \"Expected: \"\n              + Arrays.toString(fromByteArrayStream)\n              + \"\\n\"\n              + \"but got: \"\n              + Arrays.toString(fromWrappedStream)\n              + \"\\n\"\n              + writesBuilder.toString());\n    }\n  }\n\n  private Write getWrite(Random random, WriteType type) {\n    switch (type) {\n      case BYTE:\n        return getByteWrite(random);\n      case BUFFER:\n        return getBufferWrite(random);\n      case OFFSET_BUFFER:\n        return getOffsetBufferWrite(random);\n      default:\n        throw new IllegalArgumentException(\"Unrecognized type: \" + type);\n    }\n  }\n\n  private Write getOffsetBufferWrite(Random random) {\n    int dataSize = random.nextInt(MAX_BYTES_PER_WRITE * 2);\n    byte[] data = new byte[dataSize];\n    int length = dataSize == 0 ? 0 : random.nextInt(dataSize);\n    int offset = dataSize - length <= 0 ? 0 : random.nextInt(dataSize - length);\n    random.nextBytes(data);\n    return new Write(data, length, offset, WriteType.OFFSET_BUFFER);\n  }\n\n  private Write getBufferWrite(Random random) {\n    byte[] data = new byte[random.nextInt(MAX_BYTES_PER_WRITE)];\n    random.nextBytes(data);\n    return new Write(data, /* length= */ data.length, /* offset= */ 0, WriteType.BUFFER);\n  }\n\n  private Write getByteWrite(Random random) {\n    byte[] data = new byte[1];\n    random.nextBytes(data);\n    return new Write(data, /* length= */ 1, /* offset= */ 0, WriteType.BYTE);\n  }\n\n  private WriteType getType(Random random) {\n    return WriteType.values()[random.nextInt(WriteType.values().length)];\n  }\n\n  private static final class Write {\n    private final byte[] data;\n    private final int length;\n    private final int offset;\n    private final WriteType writeType;\n\n    @Override\n    public String toString() {\n      return \"Write{\"\n          + \"data=\"\n          + Arrays.toString(data)\n          + \", length=\"\n          + length\n          + \", offset=\"\n          + offset\n          + \", writeType=\"\n          + writeType\n          + '}';\n    }\n\n    Write(byte[] data, int length, int offset, WriteType writeType) {\n      this.data = data;\n      this.length = length;\n      this.offset = offset;\n      this.writeType = writeType;\n    }\n  }\n\n  private enum WriteType {\n    BYTE,\n    BUFFER,\n    OFFSET_BUFFER\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/BufferedOutputStreamTest.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.fail;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(JUnit4.class)\npublic class BufferedOutputStreamTest {\n  @Mock private ArrayPool arrayPool;\n  @Mock private OutputStream mockOutputStream;\n\n  private final int bufferSize = 10;\n  private final ByteArrayOutputStream inner = new ByteArrayOutputStream();\n  private int currentValue = 0;\n  private BufferedOutputStream os;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    when(arrayPool.get(bufferSize, byte[].class)).thenReturn(new byte[bufferSize]);\n    os = new BufferedOutputStream(inner, arrayPool, bufferSize);\n  }\n\n  @Test\n  public void constructor_obtainsBufferFromArrayPool() {\n    verify(arrayPool).get(bufferSize, byte[].class);\n  }\n\n  @Test\n  public void close_returnsBufferObtainedFromConstructor() throws IOException {\n    byte[] data = new byte[bufferSize];\n    when(arrayPool.get(bufferSize, byte[].class)).thenReturn(data);\n    os = new BufferedOutputStream(inner, arrayPool, bufferSize);\n\n    os.close();\n    verify(arrayPool).put(data);\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andSingleByte_doesNotWriteToStream() throws IOException {\n    os.write(next());\n\n    assertThat(inner.toByteArray()).isEmpty();\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataSmallerThanBuffer_doesNotWriteToStream()\n      throws IOException {\n    os.write(next(bufferSize - 1));\n\n    assertThat(inner.toByteArray()).isEmpty();\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataWithOffsetSizeSmallerThanBuffer_doesNotWriteToStream()\n      throws IOException {\n    int offset = 1;\n    int length = bufferSize - offset;\n    byte[] data = nextWithOffset(offset, length);\n    os.write(data, offset, length);\n\n    assertThat(inner.toByteArray()).isEmpty();\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataWithPaddingSizeSmallerThanBuffer_doesNotWriteToStream()\n      throws IOException {\n    int padding = 1;\n    int length = bufferSize - padding;\n    byte[] data = nextWithPadding(length, padding);\n    os.write(data, 0, length);\n\n    assertThat(inner.toByteArray()).isEmpty();\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataEqualToBufferSize_writesDataToStream()\n      throws IOException {\n    os.write(next(bufferSize));\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataGreaterThanBufferSize_writesDataToStream()\n      throws IOException {\n    os.write(next(bufferSize + 1));\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataWithOffsetAndLengthEqualToBufferSize_writesDataToStream()\n      throws IOException {\n    int offset = 5;\n    int length = bufferSize;\n    byte[] data = nextWithOffset(offset, length);\n    os.write(data, offset, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataWithPaddingAndLengthEqualToBufferSize_writesData()\n      throws IOException {\n    int padding = 5;\n    int length = bufferSize;\n    byte[] data = nextWithPadding(length, padding);\n    os.write(data, 0, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataWithOffsetAndLengthGreaterThanBuffer_writesDataToStream()\n      throws IOException {\n    int offset = 5;\n    int length = bufferSize + 1;\n    byte[] data = nextWithOffset(offset, length);\n    os.write(data, offset, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withEmptyBuffer_andDataWithPaddingAndLengthGreaterThanBuffer_writesData()\n      throws IOException {\n    int padding = 5;\n    int length = bufferSize + 1;\n    byte[] data = nextWithPadding(length, padding);\n    os.write(data, 0, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void writeSingleByte_whenBufferAlmostFull_writesBufferToStream() throws IOException {\n    for (int i = 0; i < bufferSize; i++) {\n      os.write(next());\n    }\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_withSingleByteInBuffer_writesBufferToStream() throws IOException {\n    os.write(next());\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_afterWritingByteAfterBufferFull_writesByteToStream() throws IOException {\n    for (int i = 0; i < bufferSize; i++) {\n      os.write(next());\n    }\n\n    os.write(next());\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flushAfterPreviousFlush_withSingleByte_writesOnlySingleByte() throws IOException {\n    os.write(next());\n    os.flush();\n    os.write(next());\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_withSingleByteInBuffer_writesBufferToStream() throws IOException {\n    os.write(next());\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWritingByteAfterBufferFull_writesByteToStream() throws IOException {\n    for (int i = 0; i < bufferSize; i++) {\n      os.write(next());\n    }\n\n    os.write(next());\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void closeAfterPreviousFlush_withSingleByte_writesOnlySingleByte() throws IOException {\n    os.write(next());\n    os.flush();\n    os.write(next());\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withDataInBuffer_bufferLessThanRemaining_doesNotWriteToStream()\n      throws IOException {\n    os.write(next());\n    os.write(next(remaining() - 1));\n\n    assertThat(inner.toByteArray()).isEmpty();\n  }\n\n  @Test\n  public void flush_afterWriteWithDataInBuffer_bufferLessThanRemaining_writesToStream()\n      throws IOException {\n    os.write(next());\n    byte[] data = next(remaining() - 1);\n\n    os.write(data);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteWithDataInBuffer_bufferLessThanRemaining_writesToStream()\n      throws IOException {\n    os.write(next());\n    byte[] data = next(remaining());\n\n    os.write(data);\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withBufferEqualToRemaining_lessThanLength_writesToStream() throws IOException {\n    os.write(next());\n    os.write(next(remaining()));\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_afterWriteBufferEqualToRemaining_doesNothing() throws IOException {\n    os.write(next());\n    os.write(next(remaining()));\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteBufferEqualToRemaining_doesNothing() throws IOException {\n    os.write(next());\n    os.write(next(remaining()));\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withOffsetBufferEqualToRemaining_lessThanLength_writesToStream()\n      throws IOException {\n    os.write(next());\n    int offset = 5;\n    int length = remaining();\n    os.write(nextWithOffset(offset, length), offset, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_afterWriteOffsetBufferEqualToRemaining_lessThanLength_writesToStream()\n      throws IOException {\n    os.write(next());\n    int offset = 5;\n    int length = remaining();\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteOffsetBufferEqualToRemaining_lessThanLength_writesToStream()\n      throws IOException {\n    os.write(next());\n    int offset = 5;\n    int length = remaining();\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withPaddedBufferEqualToRemaining_lessThanLength_writesToStream()\n      throws IOException {\n    os.write(next());\n    int padding = 5;\n    int length = remaining();\n    os.write(nextWithPadding(length, padding), 0, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_afterWritePaddedBufferEqualToRemaining_lessThanLength_writesToStream()\n      throws IOException {\n    os.write(next());\n    int padding = 5;\n    int length = remaining();\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWritePaddedBufferEqualToRemaining_lessThanLength_writesToStream()\n      throws IOException {\n    os.write(next());\n    int padding = 5;\n    int length = remaining();\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withBufferGreaterThanRemaining_lessThanLength_writesUpToBufferToStream()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize - 1));\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWriteBufferGreaterThanRemaining_lessThanLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize - 1));\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteBufferGreaterThanRemaining_lessThanLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize - 1));\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withOffsetBufferGreaterThanRemaining_lessThanLength_writesUpToBuffer()\n      throws IOException {\n    os.write(next(2));\n    int offset = 5;\n    int length = bufferSize - 1;\n    os.write(nextWithOffset(offset, length), offset, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWriteOffsetBufferGreaterThanRemaining_lessThanLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int offset = 5;\n    int length = bufferSize - 1;\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteOffsetBufferGreaterThanRemaining_lessThanLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int offset = 5;\n    int length = bufferSize - 1;\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withPaddedBufferGreaterThanRemaining_lessThanLength_writesUpToBuffer()\n      throws IOException {\n    os.write(next(2));\n    int padding = 5;\n    int length = bufferSize - 1;\n    os.write(nextWithPadding(length, padding), 0, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWritePaddedBufferGreaterThanRemaining_lessThanLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int padding = 5;\n    int length = bufferSize - 1;\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWritePaddedBufferGreaterThanRemaining_lessThanLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int padding = 5;\n    int length = bufferSize - 1;\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withBufferGreaterThanRemaining_equalToLength_writesUpToBufferToStream()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize));\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWriteBufferGreaterThanRemaining_equalToLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize));\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteBufferGreaterThanRemaining_equalToLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize));\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withOffsetBufferGreaterThanRemaining_equalToLength_writesUpToBufferToStream()\n      throws IOException {\n    os.write(next(2));\n    int offset = 6;\n    int length = bufferSize;\n    os.write(nextWithOffset(offset, length), offset, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWriteOffsetBufferGreaterThanRemaining_equalToLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int offset = 6;\n    int length = bufferSize;\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteOffsetBufferGreaterThanRemaining_equalToLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int offset = 6;\n    int length = bufferSize;\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withPaddedBufferGreaterThanRemaining_equalToLength_writesUpToBufferToStream()\n      throws IOException {\n    os.write(next(2));\n    int padding = 6;\n    int length = bufferSize;\n    os.write(nextWithPadding(length, padding), 0, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWritePaddedBufferGreaterThanRemaining_equalToLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int padding = 6;\n    int length = bufferSize;\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWritePaddedBufferGreaterThanRemaining_equalToLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int padding = 6;\n    int length = bufferSize;\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withBufferGreaterThanRemaining_greaterThanLength_writesUpToBufferToStream()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize + 1));\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWriteBufferGreaterThanRemaining_greaterThanLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize + 1));\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteBufferGreaterThanRemaining_greaterThanLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize + 1));\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withOffsetBufferGreaterThanRemaining_greaterThanLength_writesUpToBuffer()\n      throws IOException {\n    os.write(next(2));\n    int offset = 2;\n    int length = bufferSize + 1;\n    os.write(nextWithOffset(offset, length), offset, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWriteOffsetBufferGreaterThanRemaining_greaterThanLength_writesAllToStream()\n      throws IOException {\n    os.write(next(2));\n    int offset = 2;\n    int length = bufferSize + 1;\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteOffsetBufferGreaterThanRemaining_greaterThanLength_writesAllToStream()\n      throws IOException {\n    os.write(next(2));\n    int offset = 2;\n    int length = bufferSize + 1;\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withPaddedBufferGreaterThanRemaining_greaterThanLength_writesUpToBuffer()\n      throws IOException {\n    os.write(next(2));\n    int padding = 2;\n    int length = bufferSize + 1;\n    os.write(nextWithPadding(length, padding), 0, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(upTo(bufferSize));\n  }\n\n  @Test\n  public void flush_afterWritePaddedBufferGreaterThanRemaining_greaterThanLength_writesAllToStream()\n      throws IOException {\n    os.write(next(2));\n    int padding = 2;\n    int length = bufferSize + 1;\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWritePaddedBufferGreaterThanRemaining_greaterThanLength_writesAllToStream()\n      throws IOException {\n    os.write(next(2));\n    int padding = 2;\n    int length = bufferSize + 1;\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize * 2 + 1));\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_afterWriteBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize * 2 + 1));\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    os.write(next(bufferSize * 2 + 1));\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withOffsetBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int offset = bufferSize + 1;\n    int length = bufferSize * 2 + 2;\n    os.write(nextWithOffset(offset, length), offset, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_afterWriteOffsetBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int offset = bufferSize + 1;\n    int length = bufferSize * 2 + 2;\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWriteOffsetBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int offset = bufferSize + 1;\n    int length = bufferSize * 2 + 2;\n    os.write(nextWithOffset(offset, length), offset, length);\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_withPaddedBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int padding = bufferSize + 1;\n    int length = bufferSize * 2 + 2;\n    os.write(nextWithPadding(length, padding), 0, length);\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_afterWritePaddedBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int padding = bufferSize + 1;\n    int length = bufferSize * 2 + 2;\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void close_afterWritePaddedBufferMoreThanRemains_greaterThanTwiceLength_writesAll()\n      throws IOException {\n    os.write(next(2));\n    int padding = bufferSize + 1;\n    int length = bufferSize * 2 + 2;\n    os.write(nextWithPadding(length, padding), 0, length);\n    os.close();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void flush_flushesUnderlyingStream() throws IOException {\n    os = new BufferedOutputStream(mockOutputStream, arrayPool, bufferSize);\n    os.flush();\n\n    verify(mockOutputStream).flush();\n  }\n\n  @Test\n  public void overflowBuffer_doesNotFlushUnderlyingStream() throws IOException {\n    os = new BufferedOutputStream(mockOutputStream, arrayPool, bufferSize);\n    os.write(1);\n    os.write(next(remaining() + 1));\n\n    verify(mockOutputStream, never()).flush();\n  }\n\n  @Test\n  public void close_closesUnderlyingStream() throws IOException {\n    os = new BufferedOutputStream(mockOutputStream, arrayPool, bufferSize);\n    os.close();\n\n    verify(mockOutputStream).close();\n  }\n\n  @Test\n  public void close_whenUnderlyingStreamThrows_closesStream() throws IOException {\n    os = new BufferedOutputStream(mockOutputStream, arrayPool, bufferSize);\n    doThrow(new IOException()).when(mockOutputStream).write(any(byte[].class), anyInt(), anyInt());\n\n    os.write(1);\n    try {\n      os.close();\n      fail(\"Failed to receive expected exception\");\n    } catch (IOException e) {\n      // Expected.\n    }\n\n    verify(mockOutputStream).close();\n  }\n\n  @Test\n  public void flush_withZeroBytesWritten_doesNotWriteToStream() throws IOException {\n    os = new BufferedOutputStream(mockOutputStream, arrayPool, bufferSize);\n    os.flush();\n\n    verify(mockOutputStream, never()).write(anyInt());\n    verify(mockOutputStream, never()).write(any(byte[].class));\n    verify(mockOutputStream, never()).write(any(byte[].class), anyInt(), anyInt());\n  }\n\n  @Test\n  public void write_throwsIfOffsetIsLessThanZero() {\n    assertThrows(\n        IndexOutOfBoundsException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            os.write(new byte[0], /* initialOffset= */ -1, /* length= */ 0);\n          }\n        });\n  }\n\n  @Test\n  public void write_throwsIfLengthIsLessThanZero() {\n    assertThrows(\n        IndexOutOfBoundsException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            os.write(new byte[0], /* initialOffset= */ 0, /* length= */ -1);\n          }\n        });\n  }\n\n  @Test\n  public void write_throwsIfOffsetIsGreaterThanLength() {\n    assertThrows(\n        IndexOutOfBoundsException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            os.write(new byte[0], /* initialOffset= */ 1, /* length= */ 0);\n          }\n        });\n  }\n\n  @Test\n  public void write_throwsIfLengthsIsGreaterThanLength() {\n    assertThrows(\n        IndexOutOfBoundsException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            os.write(new byte[0], /* initialOffset= */ 0, /* length= */ 1);\n          }\n        });\n  }\n\n  @Test\n  public void write_throwsIfLengthAndOffsetsIsGreaterThanLength() {\n    assertThrows(\n        IndexOutOfBoundsException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            os.write(new byte[1], /* initialOffset= */ 1, /* length= */ 1);\n          }\n        });\n  }\n\n  @Test\n  public void write_withZeroLengthBuffer_doesNothing() throws IOException {\n    os.write(new byte[0]);\n\n    assertThat(inner.toByteArray()).hasLength(0);\n  }\n\n  @Test\n  public void write_withZeroLengthBufferAndZeroOffsetAndLength_doesNothing() throws IOException {\n    os.write(new byte[0], 0, 0);\n\n    assertThat(inner.toByteArray()).hasLength(0);\n  }\n\n  @Test\n  public void write_afterWriteWithZeroLengthBuffer_writesExpected() throws IOException {\n    os.write(new byte[0]);\n    os.write(next());\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  @Test\n  public void write_afterWriteZeroLengthBufferAndZeroOffsetAndLength_writesExpected()\n      throws IOException {\n    os.write(new byte[0], 0, 0);\n    os.write(next());\n    os.flush();\n\n    assertThat(inner.toByteArray()).isEqualTo(all());\n  }\n\n  private int soFar() {\n    return currentValue;\n  }\n\n  private int remaining() {\n    return bufferSize - soFar();\n  }\n\n  private int next() {\n    return nextWithOffset(0, 1)[0];\n  }\n\n  private byte[] next(int count) {\n    return nextWithOffset(0, count);\n  }\n\n  private byte[] nextWithPadding(int count, int padding) {\n    byte[] result = new byte[count + padding];\n    for (int i = 0; i < count; i++) {\n      result[i] = (byte) ++currentValue;\n    }\n    for (int i = count; i < count + padding; i++) {\n      result[i] = (byte) (i + currentValue);\n    }\n    return result;\n  }\n\n  private byte[] nextWithOffset(int offset, int count) {\n    byte[] result = new byte[offset + count];\n    for (int i = offset - 1; i >= 0; i--) {\n      result[i] = (byte) -offset;\n    }\n    for (int i = offset; i < offset + count; i++) {\n      result[i] = (byte) ++currentValue;\n    }\n    return result;\n  }\n\n  private byte[] upTo(int size) {\n    assertThat(size).isLessThan(currentValue);\n    byte[] result = new byte[size];\n    for (int i = 0; i < size; i++) {\n      result[i] = (byte) (i + 1);\n    }\n    return result;\n  }\n\n  private byte[] all() {\n    byte[] result = new byte[currentValue];\n    for (int i = 0; i < currentValue; i++) {\n      result[i] = (byte) (i + 1);\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/ExifOrientationStreamTest.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;\nimport com.bumptech.glide.testutil.TestResourceUtil;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ExifOrientationStreamTest {\n  private ArrayPool byteArrayPool;\n\n  private InputStream openOrientationExample(boolean isLandscape, int item) {\n    String filePrefix = isLandscape ? \"Landscape\" : \"Portrait\";\n    return TestResourceUtil.openResource(getClass(), filePrefix + \"_\" + item + \".jpg\");\n  }\n\n  @Before\n  public void setUp() {\n    byteArrayPool = new LruArrayPool();\n  }\n\n  @Test\n  public void testIncludesGivenExifOrientation() throws IOException {\n    for (int i = 0; i < 8; i++) {\n      for (int j = 0; j < 8; j++) {\n        InputStream toWrap = openOrientationExample(true /*isLandscape*/, j + 1);\n        InputStream wrapped = new ExifOrientationStream(toWrap, i);\n        DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n        assertThat(parser.getOrientation(wrapped, byteArrayPool)).isEqualTo(i);\n\n        toWrap = openOrientationExample(false /*isLandscape*/, j + 1);\n        wrapped = new ExifOrientationStream(toWrap, i);\n        assertThat(parser.getOrientation(wrapped, byteArrayPool)).isEqualTo(i);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/FileDescriptorAssetPathFetcherTest.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.AssetManager;\nimport com.bumptech.glide.Priority;\nimport java.io.IOException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class FileDescriptorAssetPathFetcherTest {\n\n  @Mock private AssetManager assetManager;\n  @Mock private AssetFileDescriptor assetFileDescriptor;\n  @Mock private DataFetcher.DataCallback<AssetFileDescriptor> callback;\n\n  private FileDescriptorAssetPathFetcher fetcher;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    String assetPath = \"/some/asset/path\";\n    fetcher = new FileDescriptorAssetPathFetcher(assetManager, assetPath);\n    when(assetManager.openFd(eq(assetPath))).thenReturn(assetFileDescriptor);\n  }\n\n  @Test\n  public void testOpensInputStreamForPathWithAssetManager() throws Exception {\n    fetcher.loadData(Priority.NORMAL, callback);\n    verify(callback).onDataReady(eq(assetFileDescriptor));\n  }\n\n  @Test\n  public void testClosesOpenedInputStreamOnCleanup() throws Exception {\n    fetcher.loadData(Priority.NORMAL, callback);\n    fetcher.cleanup();\n\n    verify(assetFileDescriptor).close();\n  }\n\n  @Test\n  public void testDoesNothingOnCleanupIfNoDataLoaded() throws IOException {\n    fetcher.cleanup();\n    verify(assetFileDescriptor, never()).close();\n  }\n\n  @Test\n  public void testDoesNothingOnCancel() throws Exception {\n    fetcher.loadData(Priority.NORMAL, callback);\n    fetcher.cancel();\n    verify(assetFileDescriptor, never()).close();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/HttpUrlFetcherServerTest.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.Headers;\nimport com.bumptech.glide.testutil.TestUtil;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport okhttp3.mockwebserver.MockResponse;\nimport okhttp3.mockwebserver.MockWebServer;\nimport okhttp3.mockwebserver.RecordedRequest;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n/**\n * Tests {@link com.bumptech.glide.load.data.HttpUrlFetcher} against server responses. Tests for\n * behavior (connection/disconnection/options) should go in {@link\n * com.bumptech.glide.load.data.HttpUrlFetcherTest}, response handling should go here.\n */\n@RunWith(RobolectricTestRunner.class)\n@Config(manifest = Config.NONE, sdk = ROBOLECTRIC_SDK)\npublic class HttpUrlFetcherServerTest {\n  private static final String DEFAULT_PATH = \"/fakepath\";\n  private static final int TIMEOUT_TIME_MS = 300;\n\n  @Mock private DataFetcher.DataCallback<InputStream> callback;\n\n  private MockWebServer mockWebServer;\n  private boolean defaultFollowRedirects;\n  private ArgumentCaptor<InputStream> streamCaptor;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    defaultFollowRedirects = HttpURLConnection.getFollowRedirects();\n    HttpURLConnection.setFollowRedirects(false);\n    mockWebServer = new MockWebServer();\n    mockWebServer.start();\n\n    streamCaptor = ArgumentCaptor.forClass(InputStream.class);\n  }\n\n  @After\n  public void tearDown() throws IOException {\n    HttpURLConnection.setFollowRedirects(defaultFollowRedirects);\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void testReturnsInputStreamOnStatusOk() throws Exception {\n    String expected = \"fakedata\";\n    mockWebServer.enqueue(new MockResponse().setBody(expected).setResponseCode(200));\n    HttpUrlFetcher fetcher = getFetcher();\n    fetcher.loadData(Priority.HIGH, callback);\n    verify(callback).onDataReady(streamCaptor.capture());\n    TestUtil.assertStreamOf(expected, streamCaptor.getValue());\n    assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo(\"GET\");\n  }\n\n  @Test\n  public void testHandlesRedirect301s() throws Exception {\n    String expected = \"fakedata\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(301)\n            .setHeader(\"Location\", mockWebServer.url(\"/redirect\").toString()));\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));\n    getFetcher().loadData(Priority.LOW, callback);\n    verify(callback).onDataReady(streamCaptor.capture());\n    TestUtil.assertStreamOf(expected, streamCaptor.getValue());\n    assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo(\"GET\");\n    assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo(\"GET\");\n  }\n\n  @Test\n  public void testHandlesRedirect302s() throws Exception {\n    String expected = \"fakedata\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(302)\n            .setHeader(\"Location\", mockWebServer.url(\"/redirect\").toString()));\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));\n    getFetcher().loadData(Priority.LOW, callback);\n    verify(callback).onDataReady(streamCaptor.capture());\n    TestUtil.assertStreamOf(expected, streamCaptor.getValue());\n    assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo(\"GET\");\n    assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo(\"GET\");\n  }\n\n  @Test\n  public void testHandlesRelativeRedirects() throws Exception {\n    String expected = \"fakedata\";\n    mockWebServer.enqueue(\n        new MockResponse().setResponseCode(301).setHeader(\"Location\", \"/redirect\"));\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));\n    getFetcher().loadData(Priority.NORMAL, callback);\n    verify(callback).onDataReady(streamCaptor.capture());\n    TestUtil.assertStreamOf(expected, streamCaptor.getValue());\n\n    RecordedRequest first = mockWebServer.takeRequest();\n    assertThat(first.getMethod()).isEqualTo(\"GET\");\n    RecordedRequest second = mockWebServer.takeRequest();\n    assertThat(second.getPath()).endsWith(\"/redirect\");\n    assertThat(second.getMethod()).isEqualTo(\"GET\");\n  }\n\n  @Test\n  public void testHandlesUpToFiveRedirects() throws Exception {\n    int numRedirects = 4;\n    String expected = \"redirectedData\";\n    String redirectBase = \"/redirect\";\n    for (int i = 0; i < numRedirects; i++) {\n      mockWebServer.enqueue(\n          new MockResponse()\n              .setResponseCode(301)\n              .setHeader(\"Location\", mockWebServer.url(redirectBase + i).toString()));\n    }\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));\n\n    getFetcher().loadData(Priority.NORMAL, callback);\n    verify(callback).onDataReady(streamCaptor.capture());\n    TestUtil.assertStreamOf(expected, streamCaptor.getValue());\n\n    RecordedRequest request = mockWebServer.takeRequest();\n    assertThat(request.getPath()).contains(DEFAULT_PATH);\n    assertThat(request.getMethod()).isEqualTo(\"GET\");\n    for (int i = 0; i < numRedirects; i++) {\n      RecordedRequest current = mockWebServer.takeRequest();\n      assertThat(current.getPath()).contains(redirectBase + i);\n      assertThat(current.getMethod()).isEqualTo(\"GET\");\n    }\n  }\n\n  @Test\n  public void testFailsOnRedirectLoops() throws Exception {\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(301)\n            .setHeader(\"Location\", mockWebServer.url(\"/redirect\").toString()));\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(301)\n            .setHeader(\"Location\", mockWebServer.url(\"/redirect\").toString()));\n\n    getFetcher().loadData(Priority.IMMEDIATE, callback);\n\n    verify(callback).onLoadFailed(isA(IOException.class));\n  }\n\n  @Test\n  public void testFailsIfRedirectLocationIsNotPresent() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(301));\n\n    getFetcher().loadData(Priority.NORMAL, callback);\n\n    verify(callback).onLoadFailed(isA(IOException.class));\n  }\n\n  @Test\n  public void testFailsIfRedirectLocationIsPresentAndEmpty() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(301).setHeader(\"Location\", \"\"));\n\n    getFetcher().loadData(Priority.NORMAL, callback);\n\n    verify(callback).onLoadFailed(isA(IOException.class));\n  }\n\n  @Test\n  public void testFailsIfStatusCodeIsNegativeOne() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(-1));\n    getFetcher().loadData(Priority.LOW, callback);\n\n    verify(callback).onLoadFailed(isA(IOException.class));\n  }\n\n  @Test\n  public void testFailsAfterTooManyRedirects() throws Exception {\n    for (int i = 0; i < 10; i++) {\n      mockWebServer.enqueue(\n          new MockResponse()\n              .setResponseCode(301)\n              .setHeader(\"Location\", mockWebServer.url(\"/redirect\" + i).toString()));\n    }\n    getFetcher().loadData(Priority.NORMAL, callback);\n\n    verify(callback).onLoadFailed(isA(IOException.class));\n  }\n\n  @Test\n  public void testFailsIfStatusCodeIs500() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(500));\n    getFetcher().loadData(Priority.NORMAL, callback);\n\n    verify(callback).onLoadFailed(isA(IOException.class));\n  }\n\n  @Test\n  public void testFailsIfStatusCodeIs400() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(400));\n    getFetcher().loadData(Priority.LOW, callback);\n\n    verify(callback).onLoadFailed(isA(IOException.class));\n  }\n\n  @Test\n  public void testSetsReadTimeout() throws Exception {\n    MockWebServer tempWebServer = new MockWebServer();\n    tempWebServer.enqueue(\n        new MockResponse().setBody(\"test\").throttleBody(1, TIMEOUT_TIME_MS, TimeUnit.MILLISECONDS));\n    tempWebServer.start();\n\n    try {\n      getFetcher().loadData(Priority.HIGH, callback);\n    } finally {\n      tempWebServer.shutdown();\n      // shutdown() called before any enqueue() blocks until it times out.\n      mockWebServer.enqueue(new MockResponse().setResponseCode(200));\n    }\n\n    verify(callback).onLoadFailed(isA(IOException.class));\n  }\n\n  @Test\n  public void testAppliesHeadersInGlideUrl() throws Exception {\n    mockWebServer.enqueue(new MockResponse().setResponseCode(200));\n    String headerField = \"field\";\n    String headerValue = \"value\";\n    Map<String, String> headersMap = new HashMap<>();\n    headersMap.put(headerField, headerValue);\n    Headers headers = mock(Headers.class);\n    when(headers.getHeaders()).thenReturn(headersMap);\n\n    getFetcher(headers).loadData(Priority.HIGH, callback);\n\n    assertThat(mockWebServer.takeRequest().getHeader(headerField)).isEqualTo(headerValue);\n  }\n\n  private HttpUrlFetcher getFetcher() {\n    return getFetcher(Headers.DEFAULT);\n  }\n\n  private HttpUrlFetcher getFetcher(Headers headers) {\n    URL url = mockWebServer.url(DEFAULT_PATH).url();\n    return new HttpUrlFetcher(\n        new GlideUrl(url, headers), TIMEOUT_TIME_MS, HttpUrlFetcher.DEFAULT_CONNECTION_FACTORY);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/HttpUrlFetcherTest.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.HttpException;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport java.io.ByteArrayInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class HttpUrlFetcherTest {\n  @Mock private HttpURLConnection urlConnection;\n  @Mock private HttpUrlFetcher.HttpUrlConnectionFactory connectionFactory;\n  @Mock private GlideUrl glideUrl;\n  @Mock private InputStream stream;\n  @Mock private DataFetcher.DataCallback<InputStream> callback;\n\n  private static final int TIMEOUT_MS = 100;\n  private HttpUrlFetcher fetcher;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    URL url = new URL(\"http://www.google.com\");\n\n    when(connectionFactory.build(eq(url))).thenReturn(urlConnection);\n    when(urlConnection.getInputStream()).thenReturn(stream);\n    when(urlConnection.getResponseCode()).thenReturn(200);\n    when(glideUrl.toURL()).thenReturn(url);\n\n    fetcher = new HttpUrlFetcher(glideUrl, TIMEOUT_MS, connectionFactory);\n  }\n\n  @Test\n  public void loadData_whenConnectThrowsFileNotFound_notifiesCallbackWithHttpErrorCode()\n      throws IOException {\n    int statusCode = 400;\n    doThrow(new FileNotFoundException()).when(urlConnection).connect();\n    when(urlConnection.getResponseCode()).thenReturn(statusCode);\n\n    fetcher.loadData(Priority.HIGH, callback);\n\n    HttpException exception = (HttpException) getCallbackException();\n    assertThat(exception.getStatusCode()).isEqualTo(statusCode);\n  }\n\n  @Test\n  public void loadData_whenGetInputStreamThrows_notifiesCallbackWithStatusCode()\n      throws IOException {\n    int statusCode = 400;\n    doThrow(new IOException()).when(urlConnection).getInputStream();\n    when(urlConnection.getResponseCode()).thenReturn(statusCode);\n\n    fetcher.loadData(Priority.HIGH, callback);\n\n    HttpException exception = (HttpException) getCallbackException();\n    assertThat(exception.getStatusCode()).isEqualTo(statusCode);\n  }\n\n  @Test\n  public void loadData_whenConnectAndGetResponseCodeThrow_notifiesCallbackWithInvalidStatusCode()\n      throws IOException {\n    doThrow(new FileNotFoundException()).when(urlConnection).connect();\n    when(urlConnection.getResponseCode()).thenThrow(new IOException());\n\n    fetcher.loadData(Priority.HIGH, callback);\n\n    HttpException exception = (HttpException) getCallbackException();\n    assertThat(exception.getStatusCode()).isEqualTo(HttpUrlFetcher.INVALID_STATUS_CODE);\n  }\n\n  @Test\n  public void loadData_whenRedirectUrlIsMalformed_notifiesCallbackWithStatusCode()\n      throws IOException {\n    int statusCode = 300;\n\n    when(urlConnection.getHeaderField(eq(HttpUrlFetcher.REDIRECT_HEADER_FIELD)))\n        .thenReturn(\"gg://www.google.com\");\n    when(urlConnection.getResponseCode()).thenReturn(statusCode);\n\n    fetcher.loadData(Priority.HIGH, callback);\n\n    HttpException exception = (HttpException) getCallbackException();\n    assertThat(exception.getStatusCode()).isEqualTo(statusCode);\n    assertThat(exception.getCause()).isInstanceOf(MalformedURLException.class);\n  }\n\n  private Exception getCallbackException() {\n    ArgumentCaptor<Exception> captor = ArgumentCaptor.forClass(Exception.class);\n    verify(callback).onLoadFailed(captor.capture());\n    return captor.getValue();\n  }\n\n  @Test\n  public void testSetsReadTimeout() {\n    fetcher.loadData(Priority.HIGH, callback);\n    verify(urlConnection).setReadTimeout(eq(TIMEOUT_MS));\n  }\n\n  @Test\n  public void testSetsConnectTimeout() {\n    fetcher.loadData(Priority.IMMEDIATE, callback);\n    verify(urlConnection).setConnectTimeout(eq(TIMEOUT_MS));\n  }\n\n  @Test\n  public void testReturnsNullIfCancelledBeforeConnects() throws IOException {\n    InputStream notExpected = new ByteArrayInputStream(new byte[0]);\n    when(urlConnection.getInputStream()).thenReturn(notExpected);\n\n    fetcher.cancel();\n    fetcher.loadData(Priority.LOW, callback);\n    verify(callback).onDataReady(ArgumentMatchers.<InputStream>isNull());\n  }\n\n  @Test\n  public void testDisconnectsUrlOnCleanup() {\n    fetcher.loadData(Priority.HIGH, callback);\n    fetcher.cleanup();\n\n    verify(urlConnection).disconnect();\n  }\n\n  @Test\n  public void testDoesNotThrowIfCleanupCalledBeforeStarted() {\n    fetcher.cleanup();\n  }\n\n  @Test\n  public void testDoesNotThrowIfCancelCalledBeforeStart() {\n    fetcher.cancel();\n  }\n\n  @Test\n  public void testCancelDoesNotDisconnectIfAlreadyConnected() {\n    fetcher.loadData(Priority.HIGH, callback);\n    fetcher.cancel();\n\n    verify(urlConnection, never()).disconnect();\n  }\n\n  @Test\n  public void testClosesStreamInCleanupIfNotNull() throws IOException {\n    fetcher.loadData(Priority.HIGH, callback);\n    fetcher.cleanup();\n\n    verify(stream).close();\n  }\n\n  @Test\n  public void testClosesStreamBeforeDisconnectingConnection() throws IOException {\n    fetcher.loadData(Priority.NORMAL, callback);\n    fetcher.cleanup();\n\n    InOrder order = inOrder(stream, urlConnection);\n    order.verify(stream).close();\n    order.verify(urlConnection).disconnect();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/LocalUriFetcherTest.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.net.Uri;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Priority;\nimport java.io.Closeable;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class LocalUriFetcherTest {\n  private TestLocalUriFetcher fetcher;\n  @Mock private DataFetcher.DataCallback<Closeable> callback;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    fetcher =\n        new TestLocalUriFetcher(\n            ApplicationProvider.getApplicationContext(), Uri.parse(\"content://empty\"));\n  }\n\n  @Test\n  public void testClosesDataOnCleanup() throws Exception {\n    fetcher.loadData(Priority.NORMAL, callback);\n    fetcher.cleanup();\n\n    verify(fetcher.closeable).close();\n  }\n\n  @Test\n  public void testDoesNotCloseNullData() throws IOException {\n    fetcher.cleanup();\n\n    verify(fetcher.closeable, never()).close();\n  }\n\n  @Test\n  public void testHandlesExceptionOnClose() throws Exception {\n    fetcher.loadData(Priority.NORMAL, callback);\n\n    doThrow(new IOException(\"Test\")).when(fetcher.closeable).close();\n    fetcher.cleanup();\n    verify(fetcher.closeable).close();\n  }\n\n  private static class TestLocalUriFetcher extends LocalUriFetcher<Closeable> {\n    final Closeable closeable = mock(Closeable.class);\n\n    TestLocalUriFetcher(Context context, Uri uri) {\n      super(context.getContentResolver(), uri);\n    }\n\n    @Override\n    protected Closeable loadResource(Uri uri, ContentResolver contentResolver)\n        throws FileNotFoundException {\n      return closeable;\n    }\n\n    @Override\n    protected void close(Closeable data) throws IOException {\n      data.close();\n    }\n\n    @NonNull\n    @Override\n    public Class<Closeable> getDataClass() {\n      return Closeable.class;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/StreamAssetPathFetcherTest.java",
    "content": "package com.bumptech.glide.load.data;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.res.AssetManager;\nimport com.bumptech.glide.Priority;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class StreamAssetPathFetcherTest {\n  @Mock private AssetManager assetManager;\n  @Mock private InputStream expected;\n  @Mock private DataFetcher.DataCallback<InputStream> callback;\n\n  private StreamAssetPathFetcher fetcher;\n\n  @Before\n  public void setUp() throws IOException {\n    MockitoAnnotations.initMocks(this);\n    String assetPath = \"/some/asset/path\";\n    fetcher = new StreamAssetPathFetcher(assetManager, assetPath);\n    when(assetManager.open(eq(assetPath))).thenReturn(expected);\n  }\n\n  @Test\n  public void testOpensInputStreamForPathWithAssetManager() throws Exception {\n    fetcher.loadData(Priority.NORMAL, callback);\n    verify(callback).onDataReady(eq(expected));\n  }\n\n  @Test\n  public void testClosesOpenedInputStreamOnCleanup() throws Exception {\n    fetcher.loadData(Priority.NORMAL, callback);\n    fetcher.cleanup();\n\n    verify(expected).close();\n  }\n\n  @Test\n  public void testDoesNothingOnCleanupIfNoDataLoaded() throws IOException {\n    fetcher.cleanup();\n    verify(expected, never()).close();\n  }\n\n  @Test\n  public void testDoesNothingOnCancel() throws Exception {\n    fetcher.loadData(Priority.NORMAL, callback);\n    fetcher.cancel();\n    verify(expected, never()).close();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/mediastore/MediaStoreUtilTest.java",
    "content": "package com.bumptech.glide.load.data.mediastore;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.net.Uri;\nimport android.provider.MediaStore;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class MediaStoreUtilTest {\n\n  @Test\n  public void isAndroidPickerUri_notAndroidPickerUri_returnsFalse() {\n    Uri mediaStoreUri = Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, \"123\");\n\n    assertThat(MediaStoreUtil.isAndroidPickerUri(mediaStoreUri)).isFalse();\n  }\n\n  @Test\n  public void isAndroidPickerUri_identifiesAndroidPickerUri_returnsTrue() {\n    Uri androidPickerUri =\n        Uri.parse(\"content://media/picker/0/com.android.providers.media.photopicker/media/123\");\n\n    assertThat(MediaStoreUtil.isAndroidPickerUri(androidPickerUri)).isTrue();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/mediastore/ThumbFetcherTest.java",
    "content": "package com.bumptech.glide.load.data.mediastore;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.net.Uri;\nimport android.provider.MediaStore;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ThumbFetcherTest {\n\n  @Mock private ThumbnailStreamOpener opener;\n  @Mock private DataFetcher.DataCallback<InputStream> callback;\n  @Mock private InputStream expected;\n\n  private ThumbFetcher fetcher;\n  private Uri uri;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    uri = Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, \"123\");\n    fetcher = new ThumbFetcher(uri, opener);\n  }\n\n  @Test\n  public void testReturnsInputStreamFromThumbnailOpener() throws Exception {\n    when(opener.open(eq(uri))).thenReturn(expected);\n\n    fetcher.loadData(Priority.LOW, callback);\n    verify(callback).onDataReady(ArgumentMatchers.<InputStream>isNotNull());\n  }\n\n  @Test\n  public void testClosesInputStreamFromThumbnailOpenerOnCleanup() throws Exception {\n    when(opener.open(eq(uri))).thenReturn(expected);\n\n    fetcher.loadData(Priority.HIGH, callback);\n\n    fetcher.cleanup();\n    verify(expected).close();\n  }\n\n  @Test\n  public void testDoesNotThrowIfCleanupWithNullInputStream() {\n    fetcher.cleanup();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/mediastore/ThumbnailStreamOpenerTest.java",
    "content": "package com.bumptech.glide.load.data.mediastore;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport android.content.ContentResolver;\nimport android.database.MatrixCursor;\nimport android.net.Uri;\nimport android.provider.MediaStore;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.fakes.RoboCursor;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ThumbnailStreamOpenerTest {\n  private Harness harness;\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Before\n  public void setUp() throws Exception {\n    harness = new Harness();\n  }\n\n  @Test\n  public void testReturnsNullIfCursorIsNull() throws FileNotFoundException {\n    when(harness.query.query(eq(harness.uri))).thenReturn(null);\n    assertNull(harness.get().open(harness.uri));\n  }\n\n  @Test\n  public void testReturnsNullIfCursorIsEmpty() throws FileNotFoundException {\n    when(harness.query.query(eq(harness.uri))).thenReturn(new MatrixCursor(new String[1]));\n    assertNull(harness.get().open(harness.uri));\n  }\n\n  @Test\n  public void testReturnsNullIfCursorHasEmptyPath() throws FileNotFoundException {\n    MatrixCursor cursor = new MatrixCursor(new String[1]);\n    cursor.addRow(new Object[] {\"\"});\n    when(harness.query.query(eq(harness.uri))).thenReturn(cursor);\n    assertNull(harness.get().open(harness.uri));\n  }\n\n  @Test\n  public void testReturnsNullIfFileDoesNotExist() throws FileNotFoundException {\n    when(harness.service.get(anyString())).thenReturn(harness.file);\n    when(harness.service.exists(eq(harness.file))).thenReturn(false);\n    assertNull(harness.get().open(harness.uri));\n  }\n\n  @Test\n  public void testReturnNullIfFileLengthIsZero() throws FileNotFoundException {\n    when(harness.service.get(anyString())).thenReturn(harness.file);\n    when(harness.service.length(eq(harness.file))).thenReturn(0L);\n    assertNull(harness.get().open(harness.uri));\n  }\n\n  @Test\n  public void testClosesCursor() throws FileNotFoundException {\n    harness.get().open(harness.uri);\n    assertTrue(harness.cursor.isClosed());\n  }\n\n  @Test\n  public void testReturnsOpenedInputStreamWhenFileFound() throws FileNotFoundException {\n    InputStream expected = new ByteArrayInputStream(new byte[0]);\n    Shadows.shadowOf(ApplicationProvider.getApplicationContext().getContentResolver())\n        .registerInputStream(harness.uri, expected);\n    assertEquals(expected, harness.get().open(harness.uri));\n  }\n\n  @Test\n  public void open_returnsNull_whenQueryThrowsSecurityException() throws FileNotFoundException {\n    when(harness.query.query(any(Uri.class))).thenThrow(new SecurityException());\n    assertThat(harness.get().open(harness.uri)).isNull();\n  }\n\n  @Test\n  public void testVideoQueryReturnsVideoCursor() {\n    Uri queryUri = MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI;\n    ThumbFetcher.VideoThumbnailQuery query =\n        new ThumbFetcher.VideoThumbnailQuery(getContentResolver());\n    RoboCursor testCursor = new RoboCursor();\n    Shadows.shadowOf(ApplicationProvider.getApplicationContext().getContentResolver())\n        .setCursor(queryUri, testCursor);\n    assertEquals(testCursor, query.query(harness.uri));\n  }\n\n  @Test\n  public void testImageQueryReturnsImageCursor() {\n    Uri queryUri = MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI;\n    ThumbFetcher.ImageThumbnailQuery query =\n        new ThumbFetcher.ImageThumbnailQuery(getContentResolver());\n    RoboCursor testCursor = new RoboCursor();\n    Shadows.shadowOf(ApplicationProvider.getApplicationContext().getContentResolver())\n        .setCursor(queryUri, testCursor);\n    assertEquals(testCursor, query.query(harness.uri));\n  }\n\n  private static ContentResolver getContentResolver() {\n    return ApplicationProvider.getApplicationContext().getContentResolver();\n  }\n\n  private class Harness {\n    final MatrixCursor cursor = new MatrixCursor(new String[1]);\n    final File file = temporaryFolder.newFile();\n    final Uri uri = Uri.fromFile(file);\n    final ThumbnailQuery query = mock(ThumbnailQuery.class);\n    final FileService service = mock(FileService.class);\n    final ArrayPool byteArrayPool = new LruArrayPool();\n\n    Harness() throws Exception {\n      cursor.addRow(new String[] {file.getAbsolutePath()});\n      when(query.query(eq(uri))).thenReturn(cursor);\n      when(service.get(eq(file.getAbsolutePath()))).thenReturn(file);\n      when(service.exists(eq(file))).thenReturn(true);\n      when(service.length(eq(file))).thenReturn(1L);\n    }\n\n    public ThumbnailStreamOpener get() {\n      List<ImageHeaderParser> parsers = new ArrayList<>();\n      parsers.add(new DefaultImageHeaderParser());\n      return new ThumbnailStreamOpener(\n          parsers, service, query, byteArrayPool, getContentResolver());\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/resource/FileDescriptorLocalUriFetcherTest.java",
    "content": "package com.bumptech.glide.load.data.resource;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.FileDescriptorLocalUriFetcher;\nimport com.bumptech.glide.load.data.mediastore.MediaStoreUtil;\nimport com.bumptech.glide.tests.ContentResolverShadow;\nimport java.io.FileNotFoundException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.shadow.api.Shadow;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(\n    sdk = ROBOLECTRIC_SDK,\n    shadows = {ContentResolverShadow.class})\npublic class FileDescriptorLocalUriFetcherTest {\n\n  @Mock private DataFetcher.DataCallback<ParcelFileDescriptor> callback;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n  }\n\n  @Test\n  public void testLoadResource_returnsFileDescriptor() throws Exception {\n    Context context = ApplicationProvider.getApplicationContext();\n    Uri uri = Uri.parse(\"file://nothing\");\n\n    ContentResolver contentResolver = context.getContentResolver();\n    ContentResolverShadow shadow = Shadow.extract(contentResolver);\n\n    AssetFileDescriptor assetFileDescriptor = mock(AssetFileDescriptor.class);\n    ParcelFileDescriptor parcelFileDescriptor = mock(ParcelFileDescriptor.class);\n    when(assetFileDescriptor.getParcelFileDescriptor()).thenReturn(parcelFileDescriptor);\n    shadow.registerFileDescriptor(uri, assetFileDescriptor);\n\n    FileDescriptorLocalUriFetcher fetcher =\n        new FileDescriptorLocalUriFetcher(context.getContentResolver(), uri, false);\n    fetcher.loadData(Priority.NORMAL, callback);\n    verify(callback).onDataReady(eq(parcelFileDescriptor));\n  }\n\n  @Test\n  public void testLoadResource_mediaUri_returnsFileDescriptor() throws Exception {\n    Context context = ApplicationProvider.getApplicationContext();\n    Uri uri = Uri.parse(\"content://media\");\n\n    ContentResolver contentResolver = context.getContentResolver();\n\n    AssetFileDescriptor assetFileDescriptor = mock(AssetFileDescriptor.class);\n    ParcelFileDescriptor parcelFileDescriptor = mock(ParcelFileDescriptor.class);\n    when(assetFileDescriptor.getParcelFileDescriptor()).thenReturn(parcelFileDescriptor);\n\n    FileDescriptorLocalUriFetcher fetcher =\n        new FileDescriptorLocalUriFetcher(\n            context.getContentResolver(), uri, /* useMediaStoreApisIfAvailable */ true);\n\n    try (MockedStatic<MediaStoreUtil> utils = Mockito.mockStatic(MediaStoreUtil.class)) {\n      utils.when(MediaStoreUtil::isMediaStoreOpenFileApisAvailable).thenReturn(true);\n      utils.when(() -> MediaStoreUtil.isMediaStoreUri(uri)).thenReturn(true);\n      utils\n          .when(() -> MediaStoreUtil.openAssetFileDescriptor(uri, contentResolver))\n          .thenReturn(assetFileDescriptor);\n      fetcher.loadData(Priority.NORMAL, callback);\n      verify(callback).onDataReady(eq(parcelFileDescriptor));\n    }\n  }\n\n  @Test\n  public void testLoadResource_withNullFileDescriptor_callsLoadFailed() {\n    Context context = ApplicationProvider.getApplicationContext();\n    Uri uri = Uri.parse(\"file://nothing\");\n\n    ContentResolver contentResolver = context.getContentResolver();\n    ContentResolverShadow shadow = Shadow.extract(contentResolver);\n    shadow.registerFileDescriptor(uri, null /*fileDescriptor*/);\n\n    FileDescriptorLocalUriFetcher fetcher =\n        new FileDescriptorLocalUriFetcher(\n            context.getContentResolver(), uri, /* useMediaStoreApisIfAvailable */ false);\n    fetcher.loadData(Priority.NORMAL, callback);\n    verify(callback).onLoadFailed(isA(FileNotFoundException.class));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/data/resource/StreamLocalUriFetcherTest.java",
    "content": "package com.bumptech.glide.load.data.resource;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.StreamLocalUriFetcher;\nimport com.bumptech.glide.load.data.mediastore.MediaStoreUtil;\nimport com.bumptech.glide.tests.ContentResolverShadow;\nimport java.io.ByteArrayInputStream;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.shadow.api.Shadow;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(\n    sdk = ROBOLECTRIC_SDK,\n    shadows = {ContentResolverShadow.class})\npublic class StreamLocalUriFetcherTest {\n  @Mock private DataFetcher.DataCallback<InputStream> callback;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n  }\n\n  @Test\n  public void testLoadResource_returnsInputStream() throws Exception {\n    Context context = ApplicationProvider.getApplicationContext();\n    Uri uri = Uri.parse(\"file://nothing\");\n\n    ContentResolver contentResolver = context.getContentResolver();\n    ContentResolverShadow shadow = Shadow.extract(contentResolver);\n    shadow.registerInputStream(uri, new ByteArrayInputStream(new byte[0]));\n\n    StreamLocalUriFetcher fetcher =\n        new StreamLocalUriFetcher(context.getContentResolver(), uri, false);\n    fetcher.loadData(Priority.NORMAL, callback);\n    verify(callback).onDataReady(ArgumentMatchers.<InputStream>isNotNull());\n  }\n\n  @Test\n  public void testLoadResource_mediaUri_returnsFileDescriptor() throws Exception {\n    Context context = ApplicationProvider.getApplicationContext();\n    Uri uri = Uri.parse(\"content://media\");\n\n    ContentResolver contentResolver = context.getContentResolver();\n\n    AssetFileDescriptor assetFileDescriptor = mock(AssetFileDescriptor.class);\n    FileInputStream inputStream = mock(FileInputStream.class);\n    when(assetFileDescriptor.createInputStream()).thenReturn(inputStream);\n\n    StreamLocalUriFetcher fetcher =\n        new StreamLocalUriFetcher(\n            context.getContentResolver(), uri, /* useMediaStoreApisIfAvailable */ true);\n\n    try (MockedStatic<MediaStoreUtil> utils = Mockito.mockStatic(MediaStoreUtil.class)) {\n      utils.when(MediaStoreUtil::isMediaStoreOpenFileApisAvailable).thenReturn(true);\n      utils.when(() -> MediaStoreUtil.isMediaStoreUri(uri)).thenReturn(true);\n      utils\n          .when(() -> MediaStoreUtil.openAssetFileDescriptor(uri, contentResolver))\n          .thenReturn(assetFileDescriptor);\n      fetcher.loadData(Priority.NORMAL, callback);\n      verify(callback).onDataReady(eq(inputStream));\n    }\n  }\n\n  @Test\n  public void testLoadResource_withNullInputStream_callsLoadFailed() {\n    Context context = ApplicationProvider.getApplicationContext();\n    Uri uri = Uri.parse(\"file://nothing\");\n\n    ContentResolver contentResolver = context.getContentResolver();\n    ContentResolverShadow shadow = Shadow.extract(contentResolver);\n\n    shadow.registerInputStream(uri, null /*inputStream*/);\n\n    StreamLocalUriFetcher fetcher =\n        new StreamLocalUriFetcher(\n            context.getContentResolver(), uri, /* useMediaStoreApisIfAvailable */ false);\n    fetcher.loadData(Priority.LOW, callback);\n\n    verify(callback).onLoadFailed(isA(FileNotFoundException.class));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/ActiveResourcesTest.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nimport android.os.Looper;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.ActiveResources.DequeuedResourceCallback;\nimport com.bumptech.glide.load.engine.ActiveResources.ResourceWeakReference;\nimport com.bumptech.glide.load.engine.EngineResource.ResourceListener;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\n\n@RunWith(RobolectricTestRunner.class)\npublic class ActiveResourcesTest {\n\n  @Mock private ResourceListener listener;\n  @Mock private Key key;\n  @Mock private Resource<Object> resource;\n\n  private ActiveResources resources;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    resources = new ActiveResources(/* isActiveResourceRetentionAllowed= */ true);\n    resources.setListener(listener);\n  }\n\n  @After\n  public void tearDown() {\n    resources.shutdown();\n  }\n\n  @Test\n  public void get_withMissingKey_returnsNull() {\n    assertThat(resources.get(key)).isNull();\n  }\n\n  @Test\n  public void get_withActiveKey_returnsResource() {\n    EngineResource<Object> expected = newCacheableEngineResource();\n    resources.activate(key, expected);\n    assertThat(resources.get(key)).isEqualTo(expected);\n  }\n\n  @Test\n  public void get_withDeactivatedKey_returnsNull() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n    resources.deactivate(key);\n    assertThat(resources.get(key)).isNull();\n  }\n\n  @Test\n  public void deactivate_withNotActiveKey_doesNotThrow() {\n    resources.deactivate(key);\n  }\n\n  @Test\n  public void get_withActiveAndClearedKey_returnsNull() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n    resources.activeEngineResources.get(key).clear();\n    assertThat(resources.get(key)).isNull();\n  }\n\n  @Test\n  public void get_withActiveAndClearedKey_andCacheableResource_callsListenerWithWrappedResource() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n    resources.activeEngineResources.get(key).clear();\n    resources.get(key);\n\n    ArgumentCaptor<EngineResource<?>> captor = getEngineResourceCaptor();\n\n    verify(listener).onResourceReleased(eq(key), captor.capture());\n\n    assertThat(captor.getValue().getResource()).isEqualTo(resource);\n  }\n\n  @Test\n  public void get_withActiveAndClearedKey_andCacheableResource_callsListenerWithNotRecycleable() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n    resources.activeEngineResources.get(key).clear();\n    resources.get(key);\n\n    ArgumentCaptor<EngineResource<?>> captor = getEngineResourceCaptor();\n\n    verify(listener).onResourceReleased(eq(key), captor.capture());\n\n    captor.getValue().recycle();\n    verify(resource, never()).recycle();\n  }\n\n  @Test\n  public void get_withActiveAndClearedKey_andCacheableResource_callsListenerWithCacheable() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n    resources.activeEngineResources.get(key).clear();\n    resources.get(key);\n\n    ArgumentCaptor<EngineResource<?>> captor = getEngineResourceCaptor();\n\n    verify(listener).onResourceReleased(eq(key), captor.capture());\n\n    assertThat(captor.getValue().isMemoryCacheable()).isTrue();\n  }\n\n  @Test\n  public void get_withActiveAndClearedKey_andNotCacheableResource_doesNotCallListener() {\n    EngineResource<Object> engineResource = newNonCacheableEngineResource();\n    resources.activate(key, engineResource);\n    resources.activeEngineResources.get(key).clear();\n    resources.get(key);\n\n    verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n  }\n\n  @Test\n  public void queueIdle_afterResourceRemovedFromActive_doesNotCallListener() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n    resources.deactivate(key);\n\n    enqueueAndWaitForRef(weakRef);\n\n    verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n  }\n\n  @Test\n  public void queueIdle_withCacheableResourceInActive_callListener() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n    enqueueAndWaitForRef(weakRef);\n\n    ArgumentCaptor<EngineResource<?>> captor = getEngineResourceCaptor();\n\n    verify(listener).onResourceReleased(eq(key), captor.capture());\n\n    EngineResource<?> released = captor.getValue();\n    assertThat(released.getResource()).isEqualTo(resource);\n    assertThat(released.isMemoryCacheable()).isTrue();\n\n    released.recycle();\n    verify(resource, never()).recycle();\n  }\n\n  @Test\n  public void queueIdle_withNotCacheableResourceInActive_doesNotCallListener() {\n    EngineResource<Object> engineResource = newNonCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n    weakRef.enqueue();\n    enqueueAndWaitForRef(weakRef);\n\n    verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n  }\n\n  @Test\n  public void queueIdle_withCacheableResourceInActive_removesResourceFromActive() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n    enqueueAndWaitForRef(weakRef);\n\n    assertThat(resources.get(key)).isNull();\n  }\n\n  @Test\n  public void queueIdle_withNotCacheableResourceInActive_removesResourceFromActive() {\n    EngineResource<Object> engineResource = newNonCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n    enqueueAndWaitForRef(weakRef);\n\n    assertThat(resources.get(key)).isNull();\n  }\n\n  @Test\n  public void queueIdle_withQueuedReferenceRetrievedFromGet_notifiesListener() {\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n\n    resources.get(key);\n\n    enqueueAndWaitForRef(weakRef);\n\n    ArgumentCaptor<EngineResource<?>> captor = getEngineResourceCaptor();\n    verify(listener).onResourceReleased(eq(key), captor.capture());\n    assertThat(captor.getValue().getResource()).isEqualTo(resource);\n  }\n\n  @Test\n  public void queueIdle_withQueuedReferenceRetrievedFromGetAndNotCacheable_doesNotNotifyListener() {\n    EngineResource<Object> engineResource = newNonCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n    CountDownLatch latch = getLatchForClearedRef();\n    weakRef.enqueue();\n\n    resources.get(key);\n\n    waitForLatch(latch);\n\n    verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n  }\n\n  @Test\n  public void queueIdle_withQueuedReferenceDeactivated_doesNotNotifyListener() {\n    final ExecutorService delegate = Executors.newSingleThreadExecutor();\n    try {\n      final CountDownLatch blockExecutor = new CountDownLatch(1);\n      resources =\n          new ActiveResources(\n              /* isActiveResourceRetentionAllowed= */ true,\n              new Executor() {\n                @Override\n                public void execute(@NonNull final Runnable command) {\n                  delegate.execute(\n                      new Runnable() {\n                        @Override\n                        public void run() {\n                          try {\n                            blockExecutor.await();\n                          } catch (InterruptedException e) {\n                            throw new RuntimeException(e);\n                          }\n                          command.run();\n                        }\n                      });\n                }\n              });\n      resources.setListener(listener);\n\n      EngineResource<Object> engineResource = newCacheableEngineResource();\n      resources.activate(key, engineResource);\n\n      ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n      CountDownLatch latch = getLatchForClearedRef();\n      weakRef.enqueue();\n      resources.deactivate(key);\n      blockExecutor.countDown();\n\n      waitForLatch(latch);\n\n      verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n    } finally {\n      resources.shutdown();\n      com.bumptech.glide.util.Executors.shutdownAndAwaitTermination(delegate);\n    }\n  }\n\n  @Test\n  public void queueIdle_afterReferenceQueuedThenReactivated_doesNotNotifyListener() {\n    final ExecutorService delegate = Executors.newSingleThreadExecutor();\n    try {\n      final CountDownLatch blockExecutor = new CountDownLatch(1);\n      resources =\n          new ActiveResources(\n              /* isActiveResourceRetentionAllowed= */ true,\n              new Executor() {\n                @Override\n                public void execute(@NonNull final Runnable command) {\n                  delegate.execute(\n                      new Runnable() {\n                        @Override\n                        public void run() {\n                          try {\n                            blockExecutor.await();\n                          } catch (InterruptedException e) {\n                            throw new RuntimeException(e);\n                          }\n                          command.run();\n                        }\n                      });\n                }\n              });\n      resources.setListener(listener);\n\n      EngineResource<Object> first = newCacheableEngineResource();\n      resources.activate(key, first);\n\n      ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n      CountDownLatch latch = getLatchForClearedRef();\n      weakRef.enqueue();\n\n      EngineResource<Object> second = newCacheableEngineResource();\n      resources.activate(key, second);\n      blockExecutor.countDown();\n\n      waitForLatch(latch);\n\n      verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n    } finally {\n      resources.shutdown();\n      com.bumptech.glide.util.Executors.shutdownAndAwaitTermination(delegate);\n    }\n  }\n\n  @Test\n  public void activate_withNonCacheableResource_doesNotSaveResource() {\n    EngineResource<Object> engineResource = newNonCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    assertThat(resources.activeEngineResources.get(key).resource).isNull();\n  }\n\n  @Test\n  public void get_withActiveClearedKey_cacheableResource_retentionDisabled_doesNotCallListener() {\n    resources = new ActiveResources(/* isActiveResourceRetentionAllowed= */ false);\n    resources.setListener(listener);\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n    resources.activeEngineResources.get(key).clear();\n    resources.get(key);\n\n    verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n  }\n\n  @Test\n  public void queueIdle_withQueuedReferenceRetrievedFromGet_retentionDisabled_doesNotNotify() {\n    resources = new ActiveResources(/* isActiveResourceRetentionAllowed= */ false);\n    resources.setListener(listener);\n    EngineResource<Object> engineResource = newCacheableEngineResource();\n    resources.activate(key, engineResource);\n\n    ResourceWeakReference weakRef = resources.activeEngineResources.get(key);\n    CountDownLatch latch = getLatchForClearedRef();\n    weakRef.enqueue();\n\n    resources.get(key);\n\n    waitForLatch(latch);\n\n    verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n  }\n\n  private void enqueueAndWaitForRef(ResourceWeakReference ref) {\n    CountDownLatch latch = getLatchForClearedRef();\n    ref.enqueue();\n    waitForLatch(latch);\n  }\n\n  private void waitForLatch(CountDownLatch latch) {\n    try {\n      latch.await(10, TimeUnit.SECONDS);\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n    Shadows.shadowOf(Looper.getMainLooper()).runToEndOfTasks();\n  }\n\n  private CountDownLatch getLatchForClearedRef() {\n    final CountDownLatch toWait = new CountDownLatch(1);\n    resources.setDequeuedResourceCallback(\n        new DequeuedResourceCallback() {\n          @Override\n          public void onResourceDequeued() {\n            toWait.countDown();\n          }\n        });\n    return toWait;\n  }\n\n  private EngineResource<Object> newCacheableEngineResource() {\n    return new EngineResource<>(\n        resource, /* isMemoryCacheable= */ true, /* isRecyclable= */ false, key, listener);\n  }\n\n  private EngineResource<Object> newNonCacheableEngineResource() {\n    return new EngineResource<>(\n        resource, /* isMemoryCacheable= */ false, /* isRecyclable= */ false, key, listener);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static ArgumentCaptor<EngineResource<?>> getEngineResourceCaptor() {\n    return (ArgumentCaptor<EngineResource<?>>)\n        (ArgumentCaptor<?>) ArgumentCaptor.forClass(EngineResource.class);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/DataCacheKeyTest.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doAnswer;\n\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util.WriteDigest;\nimport java.io.UnsupportedEncodingException;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(JUnit4.class)\npublic class DataCacheKeyTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Mock private Key firstKey;\n  @Mock private Key firstSignature;\n  @Mock private Key secondKey;\n  @Mock private Key secondSignature;\n\n  @Before\n  public void setUp() throws UnsupportedEncodingException {\n    MockitoAnnotations.initMocks(this);\n    doAnswer(new WriteDigest(\"firstKey\"))\n        .when(firstKey)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    doAnswer(new WriteDigest(\"firstSignature\"))\n        .when(firstSignature)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    doAnswer(new WriteDigest(\"secondKey\"))\n        .when(secondKey)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    doAnswer(new WriteDigest(\"secondSignature\"))\n        .when(secondSignature)\n        .updateDiskCacheKey(any(MessageDigest.class));\n  }\n\n  @Test\n  public void testEqualsHashCodeDigest() throws NoSuchAlgorithmException {\n    keyTester\n        .addEquivalenceGroup(\n            new DataCacheKey(firstKey, firstSignature), new DataCacheKey(firstKey, firstSignature))\n        .addEquivalenceGroup(new DataCacheKey(firstKey, secondSignature))\n        .addEquivalenceGroup(new DataCacheKey(secondKey, firstSignature))\n        .addEquivalenceGroup(new DataCacheKey(secondKey, secondSignature))\n        .addRegressionTest(\n            new DataCacheKey(firstKey, firstSignature),\n            \"801d7440d65a0e7c9ad0097d417f346dac4d4c4d5630724110fa3f3fe66236d9\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/EngineJobTest.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.anyResource;\nimport static com.bumptech.glide.tests.Util.isADataSource;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport androidx.core.util.Pools;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.EngineResource.ResourceListener;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.MockGlideExecutor;\nimport com.bumptech.glide.request.ResourceCallback;\nimport com.bumptech.glide.util.Executors;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.InOrder;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.shadows.ShadowLooper;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class EngineJobTest {\n  private EngineJobHarness harness;\n\n  @Before\n  public void setUp() {\n    harness = new EngineJobHarness();\n  }\n\n  @Test\n  public void testOnResourceReadyPassedToCallbacks() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    ShadowLooper.runUiThreadTasks();\n    verify(harness.cb)\n        .onResourceReady(\n            harness.engineResource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n  }\n\n  @Test\n  public void testListenerNotifiedJobCompleteOnOnResourceReady() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    ShadowLooper.runUiThreadTasks();\n\n    verify(harness.engineJobListener)\n        .onEngineJobComplete(eq(job), eq(harness.key), eq(harness.engineResource));\n  }\n\n  @Test\n  public void testNotifiesAllCallbacksOnReady() {\n    MultiCbHarness harness = new MultiCbHarness();\n    harness.job.start(harness.decodeJob);\n    harness.job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n    for (ResourceCallback cb : harness.cbs) {\n      verify(cb)\n          .onResourceReady(\n              harness.engineResource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n    }\n  }\n\n  @Test\n  public void testNotifiesAllCallbacksOnException() {\n    MultiCbHarness harness = new MultiCbHarness();\n    harness.job.start(harness.decodeJob);\n    GlideException exception = new GlideException(\"test\");\n    harness.job.onLoadFailed(exception);\n    for (ResourceCallback cb : harness.cbs) {\n      verify(cb).onLoadFailed(eq(exception));\n    }\n  }\n\n  @Test\n  public void testAcquiresResourceOncePerCallback() {\n    MultiCbHarness harness = new MultiCbHarness();\n    harness.job.start(harness.decodeJob);\n    harness.job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    // Acquired once and then released while notifying.\n    InOrder order = inOrder(harness.engineResource);\n    order.verify(harness.engineResource, times(harness.numCbs + 1)).acquire();\n    order.verify(harness.engineResource, times(1)).release();\n  }\n\n  @Test\n  public void testListenerNotifiedJobCompleteOnException() {\n    harness = new EngineJobHarness();\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onLoadFailed(new GlideException(\"test\"));\n    ShadowLooper.runUiThreadTasks();\n    verify(harness.engineJobListener)\n        .onEngineJobComplete(\n            eq(job), eq(harness.key), ArgumentMatchers.<EngineResource<?>>isNull());\n  }\n\n  @Test\n  public void testResourceIsCacheableWhenIsCacheableOnReady() {\n    harness.isCacheable = true;\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    ShadowLooper.runUiThreadTasks();\n    verify(harness.factory)\n        .build(\n            anyResource(), eq(harness.isCacheable), eq(harness.key), eq(harness.resourceListener));\n  }\n\n  @Test\n  public void testResourceIsCacheableWhenNotIsCacheableOnReady() {\n    harness.isCacheable = false;\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    ShadowLooper.runUiThreadTasks();\n    verify(harness.factory)\n        .build(\n            anyResource(), eq(harness.isCacheable), eq(harness.key), eq(harness.resourceListener));\n  }\n\n  @Test\n  public void testListenerNotifiedOfCancelOnCancel() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.cancel();\n\n    verify(harness.engineJobListener).onEngineJobCancelled(eq(job), eq(harness.key));\n  }\n\n  @Test\n  public void testOnResourceReadyNotDeliveredAfterCancel() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.cancel();\n\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    ShadowLooper.runUiThreadTasks();\n    verify(harness.cb, never()).onResourceReady(anyResource(), isADataSource(), anyBoolean());\n  }\n\n  @Test\n  public void testOnExceptionNotDeliveredAfterCancel() {\n    harness = new EngineJobHarness();\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.cancel();\n\n    job.onLoadFailed(new GlideException(\"test\"));\n\n    ShadowLooper.runUiThreadTasks();\n    verify(harness.cb, never()).onLoadFailed(any(GlideException.class));\n  }\n\n  @Test\n  public void testRemovingAllCallbacksCancelsRunner() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.removeCallback(harness.cb);\n\n    assertTrue(job.isCancelled());\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Test\n  public void removingSomeCallbacksDoesNotCancelRunner() {\n    EngineJob<Object> job = harness.getJob();\n    job.addCallback(mockResourceCallback(), Executors.directExecutor());\n    job.removeCallback(harness.cb);\n\n    assertFalse(job.isCancelled());\n  }\n\n  @Test\n  public void testResourceIsAcquiredOncePerConsumerAndOnceForCache() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    // Once while notifying and once for single callback.\n    verify(harness.engineResource, times(2)).acquire();\n  }\n\n  @Test\n  public void testDoesNotNotifyCancelledIfCompletes() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    verify(harness.engineJobListener, never()).onEngineJobCancelled(eq(job), eq(harness.key));\n  }\n\n  @Test\n  public void testDoesNotNotifyCancelledIfAlreadyCancelled() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.cancel();\n    job.cancel();\n\n    verify(harness.engineJobListener, times(1)).onEngineJobCancelled(eq(job), eq(harness.key));\n  }\n\n  @Test\n  public void testDoesNotNotifyCancelledIfReceivedException() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onLoadFailed(new GlideException(\"test\"));\n\n    verify(harness.engineJobListener)\n        .onEngineJobComplete(\n            eq(job), eq(harness.key), ArgumentMatchers.<EngineResource<?>>isNull());\n    verify(harness.engineJobListener, never())\n        .onEngineJobCancelled(any(EngineJob.class), any(Key.class));\n  }\n\n  @Test\n  public void testReleasesResourceIfCancelledOnReady() {\n    Looper looper = harness.mainHandler.getLooper();\n    Shadows.shadowOf(looper).pause();\n\n    final EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.cancel();\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    verify(harness.resource).recycle();\n  }\n\n  @Test\n  public void testDoesNotAcquireOnceForMemoryCacheIfNotCacheable() {\n    harness.isCacheable = false;\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    verify(harness.engineResource, times(2)).acquire();\n  }\n\n  @Test\n  public void testNotifiesNewCallbackOfResourceIfCallbackIsAddedDuringOnResourceReady() {\n    final EngineJob<Object> job = harness.getJob();\n    final ResourceCallback existingCallback = mockResourceCallback();\n    final ResourceCallback newCallback = mockResourceCallback();\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                job.addCallback(newCallback, Executors.directExecutor());\n                return null;\n              }\n            })\n        .when(existingCallback)\n        .onResourceReady(anyResource(), isADataSource(), anyBoolean());\n\n    job.addCallback(existingCallback, Executors.directExecutor());\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    verify(newCallback)\n        .onResourceReady(\n            harness.engineResource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n  }\n\n  @Test\n  public void testNotifiesNewCallbackOfExceptionIfCallbackIsAddedDuringOnException() {\n    harness = new EngineJobHarness();\n    final EngineJob<Object> job = harness.getJob();\n    final ResourceCallback existingCallback = mockResourceCallback();\n    final ResourceCallback newCallback = mockResourceCallback();\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                job.addCallback(newCallback, Executors.directExecutor());\n                return null;\n              }\n            })\n        .when(existingCallback)\n        .onLoadFailed(any(GlideException.class));\n\n    GlideException exception = new GlideException(\"test\");\n    job.addCallback(existingCallback, Executors.directExecutor());\n    job.start(harness.decodeJob);\n    job.onLoadFailed(exception);\n\n    verify(newCallback).onLoadFailed(eq(exception));\n  }\n\n  @Test\n  public void testRemovingCallbackDuringOnResourceReadyIsIgnoredIfCallbackHasAlreadyBeenCalled() {\n    final EngineJob<Object> job = harness.getJob();\n    final ResourceCallback cb = mockResourceCallback();\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                job.removeCallback(cb);\n                return null;\n              }\n            })\n        .when(cb)\n        .onResourceReady(anyResource(), isADataSource(), anyBoolean());\n\n    job.addCallback(cb, Executors.directExecutor());\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    verify(cb, times(1)).onResourceReady(anyResource(), isADataSource(), anyBoolean());\n  }\n\n  @Test\n  public void testRemovingCallbackDuringOnExceptionIsIgnoredIfCallbackHasAlreadyBeenCalled() {\n    harness = new EngineJobHarness();\n    final EngineJob<Object> job = harness.getJob();\n    final ResourceCallback cb = mockResourceCallback();\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                job.removeCallback(cb);\n                return null;\n              }\n            })\n        .when(cb)\n        .onLoadFailed(any(GlideException.class));\n\n    GlideException exception = new GlideException(\"test\");\n    job.addCallback(cb, Executors.directExecutor());\n    job.start(harness.decodeJob);\n    job.onLoadFailed(exception);\n\n    verify(cb, times(1)).onLoadFailed(eq(exception));\n  }\n\n  @Test\n  public void\n      testRemovingCallbackDuringOnResourceReadyPreventsCallbackFromBeingCalledIfNotYetCalled() {\n    final EngineJob<Object> job = harness.getJob();\n    final ResourceCallback notYetCalled = mockResourceCallback();\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                job.removeCallback(notYetCalled);\n                return null;\n              }\n            })\n        .when(harness.cb)\n        .onResourceReady(anyResource(), isADataSource(), anyBoolean());\n\n    job.addCallback(notYetCalled, Executors.directExecutor());\n    job.start(harness.decodeJob);\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    verify(notYetCalled, never()).onResourceReady(anyResource(), isADataSource(), anyBoolean());\n  }\n\n  @Test\n  public void\n      testRemovingCallbackDuringOnResourceReadyPreventsResourceFromBeingAcquiredForCallback() {\n    final EngineJob<Object> job = harness.getJob();\n    final ResourceCallback notYetCalled = mockResourceCallback();\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                job.removeCallback(notYetCalled);\n                return null;\n              }\n            })\n        .when(harness.cb)\n        .onResourceReady(anyResource(), isADataSource(), anyBoolean());\n\n    job.addCallback(notYetCalled, Executors.directExecutor());\n    job.start(harness.decodeJob);\n\n    job.onResourceReady(\n        harness.resource, harness.dataSource, harness.isLoadedFromAlternateCacheKey);\n\n    // Once for notifying, once for called.\n    verify(harness.engineResource, times(2)).acquire();\n  }\n\n  @Test\n  public void testRemovingCallbackDuringOnExceptionPreventsCallbackFromBeingCalledIfNotYetCalled() {\n    harness = new EngineJobHarness();\n    final EngineJob<Object> job = harness.getJob();\n    final ResourceCallback called = mockResourceCallback();\n    final ResourceCallback notYetCalled = mockResourceCallback();\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                job.removeCallback(notYetCalled);\n                return null;\n              }\n            })\n        .when(called)\n        .onLoadFailed(any(GlideException.class));\n\n    job.addCallback(called, Executors.directExecutor());\n    job.addCallback(notYetCalled, Executors.directExecutor());\n    job.start(harness.decodeJob);\n    job.onLoadFailed(new GlideException(\"test\"));\n\n    verify(notYetCalled, never()).onResourceReady(anyResource(), isADataSource(), anyBoolean());\n  }\n\n  @Test\n  public void testCancelsDecodeJobOnCancel() {\n    EngineJob<Object> job = harness.getJob();\n    job.start(harness.decodeJob);\n    job.cancel();\n\n    verify(harness.decodeJob).cancel();\n  }\n\n  @Test\n  public void testSubmitsDecodeJobToSourceServiceOnSubmitForSource() {\n    EngineJob<Object> job = harness.getJob();\n    harness.diskCacheService.shutdownNow();\n    job.reschedule(harness.decodeJob);\n\n    verify(harness.decodeJob).run();\n  }\n\n  @Test\n  public void testSubmitsDecodeJobToDiskCacheServiceWhenDecodingFromCacheOnStart() {\n    EngineJob<Object> job = harness.getJob();\n    when(harness.decodeJob.willDecodeFromCache()).thenReturn(true);\n    harness.sourceService.shutdownNow();\n    job.start(harness.decodeJob);\n\n    verify(harness.decodeJob).run();\n  }\n\n  @Test\n  public void testSubmitsDecodeJobToSourceServiceWhenDecodingFromSourceOnlyOnStart() {\n    EngineJob<Object> job = harness.getJob();\n    when(harness.decodeJob.willDecodeFromCache()).thenReturn(false);\n    harness.diskCacheService.shutdownNow();\n    job.start(harness.decodeJob);\n\n    verify(harness.decodeJob).run();\n  }\n\n  @Test\n  public void testSubmitsDecodeJobToUnlimitedSourceServiceWhenDecodingFromSourceOnlyOnStart() {\n    harness.useUnlimitedSourceGeneratorPool = true;\n    EngineJob<Object> job = harness.getJob();\n\n    when(harness.decodeJob.willDecodeFromCache()).thenReturn(false);\n    harness.diskCacheService.shutdownNow();\n    job.start(harness.decodeJob);\n\n    verify(harness.decodeJob).run();\n  }\n\n  private static ResourceCallback mockResourceCallback() {\n    ResourceCallback result = mock(ResourceCallback.class);\n    when(result.getLock()).thenReturn(result);\n    return result;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static class MultiCbHarness {\n    final Key key = mock(Key.class);\n    final Resource<Object> resource = mockResource();\n    final EngineResource<Object> engineResource = mock(EngineResource.class);\n    final EngineJobListener engineJobListener = mock(EngineJobListener.class);\n    final ResourceListener resourceListener = mock(ResourceListener.class);\n    final boolean isCacheable = true;\n    final boolean useUnlimitedSourceGeneratorPool = false;\n    final boolean useAnimationPool = false;\n    final boolean onlyRetrieveFromCache = false;\n    final int numCbs = 10;\n    final List<ResourceCallback> cbs = new ArrayList<>();\n    final EngineJob.EngineResourceFactory factory = mock(EngineJob.EngineResourceFactory.class);\n    final EngineJob<Object> job;\n    final GlideExecutor diskCacheService = MockGlideExecutor.newMainThreadExecutor();\n    final GlideExecutor sourceService = MockGlideExecutor.newMainThreadExecutor();\n    final GlideExecutor sourceUnlimitedService = MockGlideExecutor.newMainThreadExecutor();\n    final GlideExecutor animationService = MockGlideExecutor.newMainThreadExecutor();\n    final Pools.Pool<EngineJob<?>> pool = new Pools.SimplePool<>(1);\n    final DecodeJob<Object> decodeJob = mock(DecodeJob.class);\n    final DataSource dataSource = DataSource.LOCAL;\n    final boolean isLoadedFromAlternateCacheKey = true;\n\n    MultiCbHarness() {\n      when(factory.build(resource, isCacheable, key, resourceListener)).thenReturn(engineResource);\n      job =\n          new EngineJob<>(\n              diskCacheService,\n              sourceService,\n              sourceUnlimitedService,\n              animationService,\n              engineJobListener,\n              resourceListener,\n              pool,\n              factory);\n      job.init(\n          key,\n          isCacheable,\n          useUnlimitedSourceGeneratorPool,\n          useAnimationPool,\n          onlyRetrieveFromCache);\n      for (int i = 0; i < numCbs; i++) {\n        cbs.add(mockResourceCallback());\n      }\n      for (ResourceCallback cb : cbs) {\n        job.addCallback(cb, Executors.directExecutor());\n      }\n    }\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static class EngineJobHarness {\n    final EngineJob.EngineResourceFactory factory = mock(EngineJob.EngineResourceFactory.class);\n    final Key key = mock(Key.class);\n    final Handler mainHandler = new Handler();\n    final ResourceCallback cb = mockResourceCallback();\n    final Resource<Object> resource = mockResource();\n    final EngineResource<Object> engineResource = mock(EngineResource.class);\n    final EngineJobListener engineJobListener = mock(EngineJobListener.class);\n    final ResourceListener resourceListener = mock(ResourceListener.class);\n    final GlideExecutor diskCacheService = MockGlideExecutor.newMainThreadExecutor();\n    final GlideExecutor sourceService = MockGlideExecutor.newMainThreadExecutor();\n    final GlideExecutor sourceUnlimitedService = MockGlideExecutor.newMainThreadExecutor();\n    final GlideExecutor animationService = MockGlideExecutor.newMainThreadExecutor();\n    boolean isCacheable = true;\n    boolean useUnlimitedSourceGeneratorPool = false;\n    final boolean useAnimationPool = false;\n    final boolean onlyRetrieveFromCache = false;\n    final DecodeJob<Object> decodeJob = mock(DecodeJob.class);\n    final Pools.Pool<EngineJob<?>> pool = new Pools.SynchronizedPool<>(1);\n    final DataSource dataSource = DataSource.DATA_DISK_CACHE;\n    final boolean isLoadedFromAlternateCacheKey = true;\n\n    EngineJob<Object> getJob() {\n      when(factory.build(resource, isCacheable, key, resourceListener)).thenReturn(engineResource);\n      EngineJob<Object> result =\n          new EngineJob<>(\n              diskCacheService,\n              sourceService,\n              sourceUnlimitedService,\n              animationService,\n              engineJobListener,\n              resourceListener,\n              pool,\n              factory);\n      result.init(\n          key,\n          isCacheable,\n          useUnlimitedSourceGeneratorPool,\n          useAnimationPool,\n          onlyRetrieveFromCache);\n      result.addCallback(cb, Executors.directExecutor());\n      return result;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/EngineKeyTest.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertThrows;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Option.CacheKeyUpdater;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.google.common.testing.EqualsTester;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Collections;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class EngineKeyTest {\n  @Mock private Transformation<Object> transformation;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n  }\n\n  @Test\n  public void updateDiskCacheKey_throwsException() throws NoSuchAlgorithmException {\n    // If this test fails, update testEqualsAndHashcode to use KeyTester including regression tests.\n    final EngineKey key =\n        new EngineKey(\n            \"id\",\n            new ObjectKey(\"signature\"),\n            100,\n            100,\n            Collections.<Class<?>, Transformation<?>>emptyMap(),\n            Object.class,\n            Object.class,\n            new Options());\n    assertThrows(\n        UnsupportedOperationException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws NoSuchAlgorithmException {\n            key.updateDiskCacheKey(MessageDigest.getInstance(\"SHA-1\"));\n          }\n        });\n  }\n\n  @Test\n  public void testEqualsAndHashCode() {\n    Options memoryOptions = new Options();\n    memoryOptions.set(Option.memory(\"key\", new Object()), new Object());\n\n    Options diskOptions = new Options();\n    diskOptions.set(\n        Option.disk(\n            \"key\",\n            new CacheKeyUpdater<String>() {\n              @Override\n              public void update(\n                  @NonNull byte[] keyBytes,\n                  @NonNull String value,\n                  @NonNull MessageDigest messageDigest) {\n                messageDigest.update(keyBytes);\n                messageDigest.update(value.getBytes(Key.CHARSET));\n              }\n            }),\n        \"value\");\n\n    new EqualsTester()\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Object.class,\n                new Options()),\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Object.class,\n                new Options()))\n        .addEqualityGroup(\n            new EngineKey(\n                \"otherId\",\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Object.class,\n                new Options()))\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"otherSignature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Object.class,\n                new Options()))\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                200,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Object.class,\n                new Options()))\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                100,\n                200,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Object.class,\n                new Options()))\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>singletonMap(Object.class, transformation),\n                Object.class,\n                Object.class,\n                new Options()))\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Integer.class,\n                Object.class,\n                new Options()))\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Integer.class,\n                new Options()))\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Object.class,\n                memoryOptions))\n        .addEqualityGroup(\n            new EngineKey(\n                \"id\",\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                Collections.<Class<?>, Transformation<?>>emptyMap(),\n                Object.class,\n                Object.class,\n                diskOptions))\n        .testEquals();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/EngineResourceTest.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.Assert.fail;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.load.Key;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class EngineResourceTest {\n  private EngineResource<Object> engineResource;\n  @Mock private EngineResource.ResourceListener listener;\n  @Mock private Key cacheKey;\n  @Mock private Resource<Object> resource;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    engineResource =\n        new EngineResource<>(\n            resource, /* isMemoryCacheable= */ true, /* isRecyclable= */ true, cacheKey, listener);\n  }\n\n  @Test\n  public void testCanAcquireAndRelease() {\n    engineResource.acquire();\n    engineResource.release();\n\n    verify(listener).onResourceReleased(cacheKey, engineResource);\n  }\n\n  @Test\n  public void testCanAcquireMultipleTimesAndRelease() {\n    engineResource.acquire();\n    engineResource.acquire();\n    engineResource.release();\n    engineResource.release();\n\n    verify(listener).onResourceReleased(eq(cacheKey), eq(engineResource));\n  }\n\n  @Test\n  public void testDelegatesGetToWrappedResource() {\n    Object expected = new Object();\n    when(resource.get()).thenReturn(expected);\n    assertEquals(expected, engineResource.get());\n  }\n\n  @Test\n  public void testDelegatesGetSizeToWrappedResource() {\n    int expectedSize = 1234;\n    when(resource.getSize()).thenReturn(expectedSize);\n    assertEquals(expectedSize, engineResource.getSize());\n  }\n\n  @Test\n  public void testRecyclesWrappedResourceWhenRecycled() {\n    engineResource.acquire();\n    engineResource.release();\n    engineResource.recycle();\n    verify(resource).recycle();\n  }\n\n  @Test(expected = IllegalStateException.class)\n  public void testThrowsIfRecycledTwice() {\n    engineResource.recycle();\n    engineResource.recycle();\n  }\n\n  @Test(expected = IllegalStateException.class)\n  public void testThrowsIfReleasedBeforeAcquired() {\n    engineResource.release();\n  }\n\n  @Test(expected = IllegalStateException.class)\n  public void testThrowsIfRecycledWhileAcquired() {\n    engineResource.acquire();\n    engineResource.recycle();\n  }\n\n  @Test(expected = IllegalStateException.class)\n  public void testThrowsIfAcquiredAfterRecycled() {\n    engineResource.recycle();\n    engineResource.acquire();\n  }\n\n  @Test\n  public void testThrowsIfAcquiredOnBackgroundThread() throws InterruptedException {\n    Thread otherThread =\n        new Thread(\n            new Runnable() {\n              @Override\n              public void run() {\n                try {\n                  engineResource.acquire();\n                } catch (IllegalThreadStateException e) {\n                  return;\n                }\n                fail(\"Failed to receive expected IllegalThreadStateException\");\n              }\n            });\n    otherThread.start();\n    otherThread.join();\n  }\n\n  @Test\n  public void testThrowsIfReleasedOnBackgroundThread() throws InterruptedException {\n    engineResource.acquire();\n    Thread otherThread =\n        new Thread(\n            new Runnable() {\n              @Override\n              public void run() {\n                try {\n                  engineResource.release();\n                } catch (IllegalThreadStateException e) {\n                  return;\n                }\n                fail(\"Failed to receive expected IllegalThreadStateException\");\n              }\n            });\n    otherThread.start();\n    otherThread.join();\n  }\n\n  @Test(expected = IllegalStateException.class)\n  public void testThrowsIfReleasedMoreThanAcquired() {\n    engineResource.acquire();\n    engineResource.release();\n    engineResource.release();\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfWrappedResourceIsNull() {\n    new EngineResource<>(\n        /* toWrap= */ null,\n        /* isMemoryCacheable= */ false,\n        /* isRecyclable= */ true,\n        cacheKey,\n        listener);\n  }\n\n  @Test\n  public void testCanSetAndGetIsCacheable() {\n    engineResource =\n        new EngineResource<>(\n            mockResource(),\n            /* isMemoryCacheable= */ true,\n            /* isRecyclable= */ true,\n            cacheKey,\n            listener);\n    assertTrue(engineResource.isMemoryCacheable());\n    engineResource =\n        new EngineResource<>(\n            mockResource(),\n            /* isMemoryCacheable= */ false,\n            /* isRecyclable= */ true,\n            cacheKey,\n            listener);\n    assertFalse(engineResource.isMemoryCacheable());\n  }\n\n  @Test\n  public void release_whenNotRecycleable_doesNotRecycleResource() {\n    resource = mockResource();\n    engineResource =\n        new EngineResource<>(\n            resource, /* isMemoryCacheable= */ true, /* isRecyclable= */ false, cacheKey, listener);\n    engineResource.recycle();\n\n    verify(listener, never()).onResourceReleased(any(Key.class), any(EngineResource.class));\n    verify(resource, never()).recycle();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/EngineTest.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.anyResource;\nimport static com.bumptech.glide.tests.Util.isADataSource;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.GlideContext;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.cache.DiskCache;\nimport com.bumptech.glide.load.engine.cache.LruResourceCache;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.load.engine.executor.GlideExecutor;\nimport com.bumptech.glide.load.engine.executor.MockGlideExecutor;\nimport com.bumptech.glide.request.ResourceCallback;\nimport com.bumptech.glide.tests.BackgroundUtil;\nimport com.bumptech.glide.util.Executors;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\n@SuppressWarnings(\"unchecked\")\npublic class EngineTest {\n  private EngineTestHarness harness;\n\n  @Before\n  public void setUp() {\n    harness = new EngineTestHarness();\n  }\n\n  @Test\n  public void testNewRunnerIsCreatedAndPostedWithNoExistingLoad() {\n    harness.doLoad();\n\n    verify(harness.job).start((DecodeJob) any());\n  }\n\n  @Test\n  public void testCallbackIsAddedToNewEngineJobWithNoExistingLoad() {\n    harness.doLoad();\n\n    verify(harness.job).addCallback(eq(harness.cb), any(Executor.class));\n  }\n\n  @Test\n  public void testLoadStatusIsReturnedForNewLoad() {\n    assertNotNull(harness.doLoad());\n  }\n\n  @Test\n  public void testEngineJobReceivesRemoveCallbackFromLoadStatus() {\n    Engine.LoadStatus loadStatus = harness.doLoad();\n    loadStatus.cancel();\n\n    verify(harness.job).removeCallback(eq(harness.cb));\n  }\n\n  @Test\n  public void testNewRunnerIsAddedToRunnersMap() {\n    harness.doLoad();\n\n    assertThat(harness.jobs.getAll()).containsKey(harness.cacheKey);\n  }\n\n  @Test\n  public void testNewRunnerIsNotCreatedAndPostedWithExistingLoad() {\n    harness.doLoad();\n    harness.doLoad();\n\n    verify(harness.job, times(1)).start((DecodeJob) any());\n  }\n\n  @Test\n  public void testCallbackIsAddedToExistingRunnerWithExistingLoad() {\n    harness.doLoad();\n\n    ResourceCallback newCallback = mock(ResourceCallback.class);\n    harness.cb = newCallback;\n    harness.doLoad();\n\n    verify(harness.job).addCallback(eq(newCallback), any(Executor.class));\n  }\n\n  @Test\n  public void testLoadStatusIsReturnedForExistingJob() {\n    harness.doLoad();\n    Engine.LoadStatus loadStatus = harness.doLoad();\n\n    assertNotNull(loadStatus);\n  }\n\n  @Test\n  public void testResourceIsReturnedFromActiveResourcesIfPresent() {\n    harness.activeResources.activate(harness.cacheKey, harness.resource);\n\n    harness.doLoad();\n\n    verify(harness.cb)\n        .onResourceReady(eq(harness.resource), eq(DataSource.MEMORY_CACHE), eq(false));\n  }\n\n  @Test\n  public void testResourceIsAcquiredIfReturnedFromActiveResources() {\n    harness.activeResources.activate(harness.cacheKey, harness.resource);\n\n    harness.doLoad();\n\n    verify(harness.resource).acquire();\n  }\n\n  @Test\n  public void testNewLoadIsNotStartedIfResourceIsActive() {\n    harness.activeResources.activate(harness.cacheKey, harness.resource);\n\n    harness.doLoad();\n\n    verify(harness.job, never()).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void testNullLoadStatusIsReturnedIfResourceIsActive() {\n    harness.activeResources.activate(harness.cacheKey, harness.resource);\n\n    assertNull(harness.doLoad());\n  }\n\n  @Test\n  public void load_withResourceInActiveResources_doesNotCheckMemoryCache() {\n    harness.activeResources.activate(harness.cacheKey, harness.resource);\n\n    harness.doLoad();\n\n    verify(harness.cb)\n        .onResourceReady(eq(harness.resource), eq(DataSource.MEMORY_CACHE), eq(false));\n    verify(harness.cache, never()).remove(any(Key.class));\n  }\n\n  @Test\n  public void testActiveResourcesIsNotCheckedIfNotMemoryCacheable() {\n    harness.activeResources.activate(harness.cacheKey, harness.resource);\n\n    harness.isMemoryCacheable = false;\n    harness.doLoad();\n\n    verify(harness.resource, never()).acquire();\n    verify(harness.job).start((DecodeJob) any());\n  }\n\n  @Test\n  public void testCacheIsCheckedIfMemoryCacheable() {\n    when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);\n\n    harness.doLoad();\n\n    verify(harness.cb)\n        .onResourceReady(eq(harness.resource), eq(DataSource.MEMORY_CACHE), eq(false));\n  }\n\n  @Test\n  public void testCacheIsNotCheckedIfNotMemoryCacheable() {\n    when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);\n\n    harness.isMemoryCacheable = false;\n    harness.doLoad();\n\n    verify(harness.job).start((DecodeJob) any());\n  }\n\n  @Test\n  public void testResourceIsReturnedFromCacheIfPresent() {\n    when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);\n\n    harness.doLoad();\n\n    verify(harness.cb)\n        .onResourceReady(eq(harness.resource), eq(DataSource.MEMORY_CACHE), eq(false));\n  }\n\n  @Test\n  public void testHandlesNonEngineResourcesFromCacheIfPresent() {\n    final Object expected = new Object();\n    @SuppressWarnings(\"rawtypes\")\n    Resource fromCache = mockResource();\n    when(fromCache.get()).thenReturn(expected);\n    when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(fromCache);\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                Resource<?> resource = (Resource<?>) invocationOnMock.getArguments()[0];\n                assertEquals(expected, resource.get());\n                return null;\n              }\n            })\n        .when(harness.cb)\n        .onResourceReady(anyResource(), isADataSource(), anyBoolean());\n\n    harness.doLoad();\n\n    verify(harness.cb).onResourceReady(anyResource(), isADataSource(), anyBoolean());\n  }\n\n  @Test\n  public void testResourceIsAddedToActiveResourceIfReturnedFromCache() {\n    when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);\n\n    harness.doLoad();\n    EngineResource<?> activeResource = harness.activeResources.get(harness.cacheKey);\n    assertThat(activeResource).isEqualTo(harness.resource);\n  }\n\n  @Test\n  public void testResourceIsAcquiredIfReturnedFromCache() {\n    when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);\n\n    harness.doLoad();\n\n    verify(harness.resource).acquire();\n  }\n\n  @Test\n  public void testNewLoadIsNotStartedIfResourceIsCached() {\n    when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);\n\n    harness.doLoad();\n\n    verify(harness.job, never()).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void testNullLoadStatusIsReturnedForCachedResource() {\n    when(harness.cache.remove(eq(harness.cacheKey))).thenReturn(harness.resource);\n\n    Engine.LoadStatus loadStatus = harness.doLoad();\n    assertNull(loadStatus);\n  }\n\n  @Test\n  public void testRunnerIsRemovedFromRunnersOnEngineNotifiedJobComplete() {\n    harness.doLoad();\n\n    harness.callOnEngineJobComplete();\n\n    assertThat(harness.jobs.getAll()).doesNotContainKey(harness.cacheKey);\n  }\n\n  @Test\n  public void testEngineIsNotSetAsResourceListenerIfResourceIsNullOnJobComplete() {\n    harness.doLoad();\n\n    harness.getEngine().onEngineJobComplete(harness.job, harness.cacheKey, /* resource= */ null);\n  }\n\n  @Test\n  public void testResourceIsAddedToActiveResourcesOnEngineComplete() {\n    when(harness.resource.isMemoryCacheable()).thenReturn(true);\n    harness.callOnEngineJobComplete();\n\n    EngineResource<?> resource = harness.activeResources.get(harness.cacheKey);\n    assertThat(harness.resource).isEqualTo(resource);\n  }\n\n  @Test\n  public void testDoesNotPutNullResourceInActiveResourcesOnEngineComplete() {\n    harness.getEngine().onEngineJobComplete(harness.job, harness.cacheKey, /* resource= */ null);\n    assertThat(harness.activeResources.get(harness.cacheKey)).isNull();\n  }\n\n  @Test\n  public void testDoesNotPutResourceThatIsNotCacheableInActiveResourcesOnEngineComplete() {\n    when(harness.resource.isMemoryCacheable()).thenReturn(false);\n    harness.callOnEngineJobComplete();\n    assertThat(harness.activeResources.get(harness.cacheKey)).isNull();\n  }\n\n  @Test\n  public void testRunnerIsRemovedFromRunnersOnEngineNotifiedJobCancel() {\n    harness.doLoad();\n\n    harness.getEngine().onEngineJobCancelled(harness.job, harness.cacheKey);\n\n    assertThat(harness.jobs.getAll()).doesNotContainKey(harness.cacheKey);\n  }\n\n  @Test\n  public void testJobIsNotRemovedFromJobsIfOldJobIsCancelled() {\n    harness.doLoad();\n\n    harness.getEngine().onEngineJobCancelled(mock(EngineJob.class), harness.cacheKey);\n\n    assertEquals(harness.job, harness.jobs.get(harness.cacheKey, harness.onlyRetrieveFromCache));\n  }\n\n  @Test\n  public void testResourceIsAddedToCacheOnReleased() {\n    final Object expected = new Object();\n    when(harness.resource.isMemoryCacheable()).thenReturn(true);\n    when(harness.resource.get()).thenReturn(expected);\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                Resource<?> resource = (Resource<?>) invocationOnMock.getArguments()[1];\n                assertEquals(expected, resource.get());\n                return null;\n              }\n            })\n        .when(harness.cache)\n        .put(eq(harness.cacheKey), anyResource());\n\n    harness.getEngine().onResourceReleased(harness.cacheKey, harness.resource);\n\n    verify(harness.cache).put(eq(harness.cacheKey), anyResource());\n  }\n\n  @Test\n  public void testResourceIsNotAddedToCacheOnReleasedIfNotCacheable() {\n    when(harness.resource.isMemoryCacheable()).thenReturn(false);\n    harness.getEngine().onResourceReleased(harness.cacheKey, harness.resource);\n\n    verify(harness.cache, never()).put(eq(harness.cacheKey), eq(harness.resource));\n  }\n\n  @Test\n  public void testResourceIsRecycledIfNotCacheableWhenReleased() {\n    when(harness.resource.isMemoryCacheable()).thenReturn(false);\n    harness.getEngine().onResourceReleased(harness.cacheKey, harness.resource);\n    verify(harness.resourceRecycler).recycle(eq(harness.resource), eq(false));\n  }\n\n  @Test\n  public void testResourceIsRemovedFromActiveResourcesWhenReleased() {\n    harness.activeResources.activate(harness.cacheKey, harness.resource);\n\n    harness.getEngine().onResourceReleased(harness.cacheKey, harness.resource);\n\n    assertThat(harness.activeResources.get(harness.cacheKey)).isNull();\n  }\n\n  @Test\n  public void testEngineAddedAsListenerToMemoryCache() {\n    harness.getEngine();\n    verify(harness.cache).setResourceRemovedListener(eq(harness.getEngine()));\n  }\n\n  @Test\n  public void testResourceIsRecycledWhenRemovedFromCache() {\n    harness.getEngine().onResourceRemoved(harness.resource);\n    verify(harness.resourceRecycler).recycle(eq(harness.resource), eq(true));\n  }\n\n  @Test\n  public void testJobIsPutInJobWithCacheKeyWithRelevantIds() {\n    harness.doLoad();\n\n    assertThat(harness.jobs.getAll()).containsEntry(harness.cacheKey, harness.job);\n  }\n\n  @Test\n  public void testKeyFactoryIsGivenNecessaryArguments() {\n    harness.doLoad();\n\n    verify(harness.keyFactory)\n        .buildKey(\n            eq(harness.model),\n            eq(harness.signature),\n            eq(harness.width),\n            eq(harness.height),\n            eq(harness.transformations),\n            eq(Object.class),\n            eq(Object.class),\n            eq(harness.options));\n  }\n\n  @Test\n  public void testFactoryIsGivenNecessaryArguments() {\n    harness.doLoad();\n\n    verify(harness.engineJobFactory)\n        .build(\n            eq(harness.cacheKey),\n            eq(true) /*isMemoryCacheable*/,\n            eq(false) /*useUnlimitedSourceGeneratorPool*/,\n            /* useAnimationPool= */ eq(false),\n            /* onlyRetrieveFromCache= */ eq(false));\n  }\n\n  @Test\n  public void testFactoryIsGivenNecessaryArgumentsWithUnlimitedPool() {\n    harness.useUnlimitedSourceGeneratorPool = true;\n    harness.doLoad();\n\n    verify(harness.engineJobFactory)\n        .build(\n            eq(harness.cacheKey),\n            eq(true) /*isMemoryCacheable*/,\n            eq(true) /*useUnlimitedSourceGeneratorPool*/,\n            /* useAnimationPool= */ eq(false),\n            /* onlyRetrieveFromCache= */ eq(false));\n  }\n\n  @Test\n  public void testReleaseReleasesEngineResource() {\n    EngineResource<Object> engineResource = mock(EngineResource.class);\n    harness.getEngine().release(engineResource);\n    verify(engineResource).release();\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfAskedToReleaseNonEngineResource() {\n    harness.getEngine().release(mockResource());\n  }\n\n  @Test\n  public void load_whenCalledOnBackgroundThread_doesNotThrow() throws InterruptedException {\n    BackgroundUtil.testInBackground(\n        new BackgroundUtil.BackgroundTester() {\n          @Override\n          public void runTest() {\n            harness.doLoad();\n          }\n        });\n  }\n\n  @Test\n  public void load_afterResourceIsLoadedInActiveResources_returnsFromMemoryCache() {\n    when(harness.resource.isMemoryCacheable()).thenReturn(true);\n    doAnswer(\n            new Answer<Object>() {\n              @Override\n              public Object answer(InvocationOnMock invocationOnMock) {\n                harness.callOnEngineJobComplete();\n                return null;\n              }\n            })\n        .when(harness.job)\n        .start(anyDecodeJobOrNull());\n    harness.doLoad();\n    harness.doLoad();\n    verify(harness.cb).onResourceReady(any(Resource.class), eq(DataSource.MEMORY_CACHE), eq(false));\n  }\n\n  @Test\n  public void load_afterResourceIsLoadedAndReleased_returnsFromMemoryCache() {\n    harness.cache = new LruResourceCache(100);\n    when(harness.resource.isMemoryCacheable()).thenReturn(true);\n    doAnswer(\n            new Answer<Object>() {\n              @Override\n              public Object answer(InvocationOnMock invocationOnMock) {\n                harness.callOnEngineJobComplete();\n                return null;\n              }\n            })\n        .when(harness.job)\n        .start(anyDecodeJobOrNull());\n    harness.doLoad();\n    harness.getEngine().onResourceReleased(harness.cacheKey, harness.resource);\n    harness.doLoad();\n    verify(harness.cb).onResourceReady(any(Resource.class), eq(DataSource.MEMORY_CACHE), eq(false));\n  }\n\n  @Test\n  public void load_withOnlyRetrieveFromCache_andPreviousNormalLoad_startsNewLoad() {\n    EngineJob<?> first = harness.job;\n    harness.doLoad();\n    EngineJob<?> second = mock(EngineJob.class);\n    harness.job = second;\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n\n    verify(first).start(anyDecodeJobOrNull());\n    verify(second).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void load_withNormalLoad_afterPreviousRetrieveFromCache_startsNewLoad() {\n    EngineJob<?> first = harness.job;\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n    EngineJob<?> second = mock(EngineJob.class);\n    harness.job = second;\n    harness.onlyRetrieveFromCache = false;\n    harness.doLoad();\n\n    verify(first).start(anyDecodeJobOrNull());\n    verify(second).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void load_afterFinishedOnlyRetrieveFromCache_withPendingNormal_doesNotStartNewLoad() {\n    EngineJob<?> firstNormal = harness.job;\n    harness.doLoad();\n\n    harness.job = mock(EngineJob.class);\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n    harness.callOnEngineJobComplete();\n\n    EngineJob<?> secondNormal = mock(EngineJob.class);\n    harness.job = secondNormal;\n    harness.onlyRetrieveFromCache = false;\n    harness.doLoad();\n\n    verify(firstNormal).start(anyDecodeJobOrNull());\n    verify(secondNormal, never()).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void load_afterCancelledOnlyRetrieveFromCache_withPendingNormal_doesNotStartNewLoad() {\n    EngineJob<?> firstNormal = harness.job;\n    harness.doLoad();\n\n    harness.job = mock(EngineJob.class);\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n    harness.getEngine().onEngineJobCancelled(harness.job, harness.cacheKey);\n\n    EngineJob<?> secondNormal = mock(EngineJob.class);\n    harness.job = secondNormal;\n    harness.onlyRetrieveFromCache = false;\n    harness.doLoad();\n\n    verify(firstNormal).start(anyDecodeJobOrNull());\n    verify(secondNormal, never()).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void load_withOnlyRetrieveFromCache_withOtherRetrieveFromCachePending_doesNotStartNew() {\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n\n    EngineJob<?> second = mock(EngineJob.class);\n    harness.job = second;\n    harness.doLoad();\n\n    verify(second, never()).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void load_withOnlyRetrieveFromCache_afterPreviousFinishedOnlyFromCacheLoad_startsNew() {\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n    harness.callOnEngineJobComplete();\n\n    EngineJob<?> second = mock(EngineJob.class);\n    harness.job = second;\n    harness.doLoad();\n\n    verify(second).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void load_withOnlyRetrieveFromCache_afterPreviousCancelledOnlyFromCacheLoad_startsNew() {\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n    harness.getEngine().onEngineJobCancelled(harness.job, harness.cacheKey);\n\n    EngineJob<?> second = mock(EngineJob.class);\n    harness.job = second;\n    harness.doLoad();\n\n    verify(second).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void onEngineJobComplete_withOldJobForKey_doesNotRemoveJob() {\n    harness.doLoad();\n    harness\n        .getEngine()\n        .onEngineJobComplete(mock(EngineJob.class), harness.cacheKey, harness.resource);\n\n    harness.job = mock(EngineJob.class);\n    harness.doLoad();\n\n    verify(harness.job, never()).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void onEngineJobCancelled_withOldJobForKey_doesNotRemoveJob() {\n    harness.doLoad();\n    harness.getEngine().onEngineJobCancelled(mock(EngineJob.class), harness.cacheKey);\n\n    harness.job = mock(EngineJob.class);\n    harness.doLoad();\n\n    verify(harness.job, never()).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void onEngineJobComplete_withOnlyRetrieveFromCacheAndOldJobForKey_doesNotRemoveJob() {\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n    harness\n        .getEngine()\n        .onEngineJobComplete(mock(EngineJob.class), harness.cacheKey, harness.resource);\n\n    harness.job = mock(EngineJob.class);\n    harness.doLoad();\n\n    verify(harness.job, never()).start(anyDecodeJobOrNull());\n  }\n\n  @Test\n  public void onEngineJobCancelled_withOnlyRetrieveFromCacheAndOldJobForKey_doesNotRemoveJob() {\n    harness.onlyRetrieveFromCache = true;\n    harness.doLoad();\n    harness.getEngine().onEngineJobCancelled(mock(EngineJob.class), harness.cacheKey);\n\n    harness.job = mock(EngineJob.class);\n    harness.doLoad();\n\n    verify(harness.job, never()).start(anyDecodeJobOrNull());\n  }\n\n  @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n  private static DecodeJob anyDecodeJobOrNull() {\n    return any();\n  }\n\n  private static class EngineTestHarness {\n    final EngineKey cacheKey = mock(EngineKey.class);\n    final EngineKeyFactory keyFactory = mock(EngineKeyFactory.class);\n    ResourceCallback cb = mock(ResourceCallback.class);\n\n    @SuppressWarnings(\"rawtypes\")\n    final EngineResource resource = mock(EngineResource.class);\n\n    final Jobs jobs = new Jobs();\n    final ActiveResources activeResources =\n        new ActiveResources(/* isActiveResourceRetentionAllowed= */ true);\n\n    final int width = 100;\n    final int height = 100;\n\n    final Object model = new Object();\n    MemoryCache cache = mock(MemoryCache.class);\n    EngineJob<?> job;\n    private Engine engine;\n    final Engine.EngineJobFactory engineJobFactory = mock(Engine.EngineJobFactory.class);\n    final Engine.DecodeJobFactory decodeJobFactory = mock(Engine.DecodeJobFactory.class);\n    final ResourceRecycler resourceRecycler = mock(ResourceRecycler.class);\n    final Key signature = mock(Key.class);\n    final Map<Class<?>, Transformation<?>> transformations = new HashMap<>();\n    final Options options = new Options();\n    final GlideContext glideContext = mock(GlideContext.class);\n    boolean isMemoryCacheable = true;\n    boolean useUnlimitedSourceGeneratorPool = false;\n    boolean onlyRetrieveFromCache = false;\n    final boolean isScaleOnlyOrNoTransform = true;\n\n    EngineTestHarness() {\n      when(keyFactory.buildKey(\n              eq(model),\n              eq(signature),\n              anyInt(),\n              anyInt(),\n              eq(transformations),\n              eq(Object.class),\n              eq(Object.class),\n              eq(options)))\n          .thenReturn(cacheKey);\n      when(resource.getResource()).thenReturn(mock(Resource.class));\n\n      job = mock(EngineJob.class);\n    }\n\n    void callOnEngineJobComplete() {\n      getEngine().onEngineJobComplete(job, cacheKey, resource);\n    }\n\n    Engine.LoadStatus doLoad() {\n      when(engineJobFactory.build(\n              eq(cacheKey), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean()))\n          .thenReturn((EngineJob<Object>) job);\n      when(job.onlyRetrieveFromCache()).thenReturn(onlyRetrieveFromCache);\n      return getEngine()\n          .load(\n              glideContext,\n              model,\n              signature,\n              width,\n              height,\n              Object.class /*resourceClass*/,\n              Object.class /*transcodeClass*/,\n              Priority.HIGH,\n              DiskCacheStrategy.ALL,\n              transformations,\n              false /*isTransformationRequired*/,\n              isScaleOnlyOrNoTransform,\n              options,\n              isMemoryCacheable,\n              useUnlimitedSourceGeneratorPool,\n              /* useAnimationPool= */ false,\n              onlyRetrieveFromCache,\n              cb,\n              Executors.directExecutor());\n    }\n\n    Engine getEngine() {\n      if (engine == null) {\n        engine =\n            new Engine(\n                cache,\n                mock(DiskCache.Factory.class),\n                GlideExecutor.newDiskCacheExecutor(),\n                MockGlideExecutor.newMainThreadExecutor(),\n                MockGlideExecutor.newMainThreadExecutor(),\n                MockGlideExecutor.newMainThreadExecutor(),\n                jobs,\n                keyFactory,\n                activeResources,\n                engineJobFactory,\n                decodeJobFactory,\n                resourceRecycler,\n                /* isActiveResourceRetentionAllowed= */ true);\n      }\n      return engine;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/ResourceCacheKeyTest.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doAnswer;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Option.CacheKeyUpdater;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport java.util.Arrays;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class ResourceCacheKeyTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Mock private Transformation<Object> transformation1;\n  @Mock private Transformation<Object> transformation2;\n  private LruArrayPool arrayPool;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    arrayPool = new LruArrayPool();\n    doAnswer(new Util.WriteDigest(\"transformation1\"))\n        .when(transformation1)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    doAnswer(new Util.WriteDigest(\"transformation1\"))\n        .when(transformation2)\n        .updateDiskCacheKey(any(MessageDigest.class));\n  }\n\n  @Test\n  public void testEqualsAndHashCode() {\n    Options memoryOptions = new Options();\n    memoryOptions.set(Option.memory(\"key\", new Object()), new Object());\n\n    Options diskOptions = new Options();\n    diskOptions.set(\n        Option.disk(\n            \"key\",\n            new CacheKeyUpdater<String>() {\n              @Override\n              public void update(\n                  @NonNull byte[] keyBytes,\n                  @NonNull String value,\n                  @NonNull MessageDigest messageDigest) {\n                messageDigest.update(keyBytes);\n                messageDigest.update(value.getBytes(Key.CHARSET));\n              }\n            }),\n        \"value\");\n\n    for (int i = 0; i < 20; i++) {\n      byte[] array = new byte[9];\n      Arrays.fill(array, (byte) 2);\n      arrayPool.put(array);\n    }\n\n    keyTester\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation1,\n                Object.class,\n                new Options()),\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation1,\n                Object.class,\n                new Options()))\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"otherSource\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation1,\n                Object.class,\n                new Options()))\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"otherSignature\"),\n                100,\n                100,\n                transformation1,\n                Object.class,\n                new Options()))\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                200,\n                100,\n                transformation1,\n                Object.class,\n                new Options()))\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                200,\n                transformation1,\n                Object.class,\n                new Options()))\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation2,\n                Object.class,\n                new Options()))\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation1,\n                Integer.class,\n                new Options()))\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation1,\n                Object.class,\n                memoryOptions))\n        .addEquivalenceGroup(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation1,\n                Object.class,\n                diskOptions))\n        .addRegressionTest(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation1,\n                Object.class,\n                new Options()),\n            \"04d632bfe8e588544909fc44edb7328fa28bea6831b96927ade22b44818654e2\")\n        .addRegressionTest(\n            new ResourceCacheKey(\n                arrayPool,\n                new ObjectKey(\"source\"),\n                new ObjectKey(\"signature\"),\n                100,\n                100,\n                transformation1,\n                Object.class,\n                diskOptions),\n            \"781ff8cd30aaaf248134580004ea6d63a1b87ae20ea0f769caf379d7d84986d0\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/ResourceRecyclerTest.java",
    "content": "package com.bumptech.glide.load.engine;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nimport android.os.Looper;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ResourceRecyclerTest {\n\n  private ResourceRecycler recycler;\n\n  @Before\n  public void setUp() {\n    recycler = new ResourceRecycler();\n  }\n\n  @Test\n  public void recycle_withoutForceNextFrame_recyclesResourceSynchronously() {\n    Resource<?> resource = mockResource();\n    Shadows.shadowOf(Looper.getMainLooper()).pause();\n    recycler.recycle(resource, /* forceNextFrame= */ false);\n    verify(resource).recycle();\n  }\n\n  @Test\n  public void recycle_withForceNextFrame_postsRecycle() {\n    Resource<?> resource = mockResource();\n    Shadows.shadowOf(Looper.getMainLooper()).pause();\n    recycler.recycle(resource, /* forceNextFrame= */ true);\n    verify(resource, never()).recycle();\n    Shadows.shadowOf(Looper.getMainLooper()).runToEndOfTasks();\n    verify(resource).recycle();\n  }\n\n  @Test\n  public void testDoesNotRecycleChildResourceSynchronously() {\n    Resource<?> parent = mockResource();\n    final Resource<?> child = mockResource();\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) throws Throwable {\n                recycler.recycle(child, /* forceNextFrame= */ false);\n                return null;\n              }\n            })\n        .when(parent)\n        .recycle();\n\n    Shadows.shadowOf(Looper.getMainLooper()).pause();\n\n    recycler.recycle(parent, /* forceNextFrame= */ false);\n\n    verify(parent).recycle();\n    verify(child, never()).recycle();\n\n    Shadows.shadowOf(Looper.getMainLooper()).runOneTask();\n\n    verify(child).recycle();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/bitmap_recycle/AttributeStrategyKeyTest.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotEquals;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport android.graphics.Bitmap;\nimport com.bumptech.glide.load.engine.bitmap_recycle.AttributeStrategy.Key;\nimport com.google.common.testing.EqualsTester;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class AttributeStrategyKeyTest {\n\n  private AttributeStrategy.KeyPool keyPool;\n\n  @Before\n  public void setUp() {\n    keyPool = mock(AttributeStrategy.KeyPool.class);\n  }\n\n  @Test\n  public void testEquality() {\n    Key first = new Key(keyPool);\n    first.init(100, 100, Bitmap.Config.ARGB_4444);\n    Key second = new Key(keyPool);\n    second.init(100, 100, Bitmap.Config.ARGB_4444);\n\n    Key third = new Key(keyPool);\n    third.init(200, 100, Bitmap.Config.ARGB_4444);\n\n    Key fourth = new Key(keyPool);\n    fourth.init(100, 200, Bitmap.Config.ARGB_4444);\n\n    Key fifth = new Key(keyPool);\n    fifth.init(100, 100, Bitmap.Config.RGB_565);\n\n    new EqualsTester()\n        .addEqualityGroup(first, second)\n        .addEqualityGroup(third)\n        .addEqualityGroup(fourth)\n        .addEqualityGroup(fifth)\n        .testEquals();\n  }\n\n  @Test\n  public void testReturnsSelfToPoolOnOffer() {\n    Key key = new Key(keyPool);\n    key.offer();\n\n    verify(keyPool).offer(eq(key));\n  }\n\n  @Test\n  public void testInitSetsAttributes() {\n    Key key = new Key(keyPool);\n    key.init(100, 100, Bitmap.Config.ARGB_4444);\n\n    Key other = new Key(keyPool);\n    other.init(200, 200, Bitmap.Config.RGB_565);\n\n    assertNotEquals(key, other);\n\n    key.init(200, 200, Bitmap.Config.RGB_565);\n\n    assertEquals(key, other);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/bitmap_recycle/AttributeStrategyTest.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\nimport android.graphics.Bitmap;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class AttributeStrategyTest {\n\n  private AttributeStrategy strategy;\n\n  @Before\n  public void setUp() throws Exception {\n    strategy = new AttributeStrategy();\n  }\n\n  @Test\n  public void testIGetNullIfNoMatchingBitmapExists() {\n    assertNull(strategy.get(100, 100, Bitmap.Config.ARGB_8888));\n  }\n\n  @Test\n  public void testICanAddAndGetABitmapOfTheSameSizeAndDimensions() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    strategy.put(bitmap);\n    assertEquals(\n        bitmap, strategy.get(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888));\n  }\n\n  @Test\n  public void testICantGetABitmapOfTheSameDimensionsButDifferentConfigs() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    strategy.put(bitmap);\n    assertNull(strategy.get(100, 100, Bitmap.Config.RGB_565));\n  }\n\n  @Test\n  public void testICantGetABitmapOfTheSameDimensionsAndSizeButDifferentConfigs() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_4444);\n    strategy.put(bitmap);\n    assertNull(strategy.get(100, 100, Bitmap.Config.RGB_565));\n  }\n\n  @Test\n  public void testICantGetABitmapOfDifferentWidths() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    strategy.put(bitmap);\n    assertNull(strategy.get(99, 100, Bitmap.Config.ARGB_8888));\n  }\n\n  @Test\n  public void testICantGetABitmapOfDifferentHeights() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    strategy.put(bitmap);\n    assertNull(strategy.get(100, 99, Bitmap.Config.ARGB_8888));\n  }\n\n  @Test\n  public void testICantGetABitmapOfDifferentDimensionsButTheSameSize() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    strategy.put(bitmap);\n    assertNull(strategy.get(50, 200, Bitmap.Config.ARGB_8888));\n  }\n\n  @Test\n  public void testMultipleBitmapsOfDifferentAttributesCanBeAddedAtOnce() {\n    Bitmap first = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);\n    Bitmap second = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    Bitmap third = Bitmap.createBitmap(120, 120, Bitmap.Config.RGB_565);\n\n    strategy.put(first);\n    strategy.put(second);\n    strategy.put(third);\n\n    assertEquals(first, strategy.get(100, 100, Bitmap.Config.RGB_565));\n    assertEquals(second, strategy.get(100, 100, Bitmap.Config.ARGB_8888));\n    assertEquals(third, strategy.get(120, 120, Bitmap.Config.RGB_565));\n  }\n\n  @Test\n  public void testLeastRecentlyUsedAttributeSetIsRemovedFirst() {\n    final Bitmap leastRecentlyUsed = Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8);\n    final Bitmap other = Bitmap.createBitmap(1000, 1000, Bitmap.Config.RGB_565);\n    final Bitmap mostRecentlyUsed = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    strategy.get(100, 100, Bitmap.Config.ALPHA_8);\n    strategy.get(1000, 1000, Bitmap.Config.RGB_565);\n    strategy.get(100, 100, Bitmap.Config.ARGB_8888);\n\n    strategy.put(other);\n    strategy.put(leastRecentlyUsed);\n    strategy.put(mostRecentlyUsed);\n\n    Bitmap removed = strategy.removeLast();\n    assertEquals(\n        \"Expected=\" + strategy.logBitmap(leastRecentlyUsed) + \" got=\" + strategy.logBitmap(removed),\n        leastRecentlyUsed,\n        removed);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/bitmap_recycle/GroupedLinkedMapTest.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertNull;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class GroupedLinkedMapTest {\n\n  private GroupedLinkedMap<Key, Object> map;\n\n  @Before\n  public void setUp() {\n    map = new GroupedLinkedMap<>();\n  }\n\n  @Test\n  public void testReturnsNullForGetWithNoBitmap() {\n    Key key = new Key(\"key\", /* width= */ 1, /* height= */ 1);\n    assertNull(map.get(key));\n  }\n\n  @Test\n  public void testCanAddAndRemoveABitmap() {\n    Key key = new Key(\"key\", 1, 1);\n    Object expected = new Object();\n\n    map.put(key, expected);\n\n    assertThat(map.get(key)).isEqualTo(expected);\n  }\n\n  @Test\n  public void testCanAddAndRemoveMoreThanOneBitmapForAGivenKey() {\n    Key key = new Key(\"key\", 1, 1);\n    Integer value = 20;\n\n    int numToAdd = 10;\n\n    for (int i = 0; i < numToAdd; i++) {\n      map.put(key, value);\n    }\n\n    for (int i = 0; i < numToAdd; i++) {\n      assertThat(map.get(key)).isEqualTo(value);\n    }\n  }\n\n  @Test\n  public void testLeastRecentlyRetrievedKeyIsLeastRecentlyUsed() {\n    Key firstKey = new Key(\"key\", 1, 1);\n    Integer firstValue = 10;\n    map.put(firstKey, firstValue);\n    map.put(firstKey, firstValue);\n\n    Key secondKey = new Key(\"key\", 2, 2);\n    Integer secondValue = 20;\n    map.put(secondKey, secondValue);\n\n    map.get(firstKey);\n\n    assertThat(map.removeLast()).isEqualTo(secondValue);\n  }\n\n  @Test\n  public void testAddingAnEntryDoesNotMakeItMostRecentlyUsed() {\n    Key firstKey = new Key(\"key\", 1, 1);\n    Integer firstValue = 10;\n\n    map.put(firstKey, firstValue);\n    map.put(firstKey, firstValue);\n\n    map.get(firstKey);\n\n    Integer secondValue = 20;\n    map.put(new Key(\"key\", 2, 2), secondValue);\n\n    assertThat(map.removeLast()).isEqualTo(secondValue);\n  }\n\n  private static final class Key implements Poolable {\n\n    private final String key;\n    private final int width;\n    private final int height;\n\n    Key(String key, int width, int height) {\n      this.key = key;\n      this.width = width;\n      this.height = height;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof Key) {\n        Key other = (Key) o;\n        return key.equals(other.key) && width == other.width && height == other.height;\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      int result = key != null ? key.hashCode() : 0;\n      result = 31 * result + width;\n      result = 31 * result + height;\n      return result;\n    }\n\n    @Override\n    public void offer() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/bitmap_recycle/LruArrayPoolTest.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;\nimport static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;\nimport static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;\nimport static android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class LruArrayPoolTest {\n  private static final int MAX_SIZE = 10;\n  private static final int MAX_PUT_SIZE = MAX_SIZE / 2;\n  private static final Class<byte[]> ARRAY_CLASS = byte[].class;\n  private static final ArrayAdapterInterface<byte[]> ADAPTER = new ByteArrayAdapter();\n  private LruArrayPool pool;\n\n  @Before\n  public void setUp() {\n    pool = new LruArrayPool(MAX_SIZE);\n  }\n\n  @Test\n  public void testNewPoolIsEmpty() {\n    assertEquals(pool.getCurrentSize(), 0);\n  }\n\n  @Test\n  public void testICanAddAndGetValidArray() {\n    int size = 758;\n    int value = 564;\n    fillPool(pool, size - 1, value);\n    pool.put(createArray(ARRAY_CLASS, size, value));\n    Object array = pool.get(size, ARRAY_CLASS);\n    assertNotNull(array);\n    assertTrue(array.getClass() == ARRAY_CLASS);\n    assertTrue(ADAPTER.getArrayLength((byte[]) array) >= size);\n    assertTrue(((byte[]) array)[0] == (byte) 0);\n  }\n\n  @Test\n  public void testItIsSizeLimited() {\n    fillPool(pool, MAX_SIZE / ADAPTER.getElementSizeInBytes() + 1, 1);\n    assertTrue(pool.getCurrentSize() <= MAX_SIZE);\n  }\n\n  @Test\n  public void testArrayLargerThanPoolIsNotAdded() {\n    pool = new LruArrayPool(MAX_SIZE);\n    pool.put(createArray(ARRAY_CLASS, MAX_SIZE / ADAPTER.getElementSizeInBytes() + 1, 0));\n    assertEquals(0, pool.getCurrentSize());\n  }\n\n  @Test\n  public void testClearMemoryRemovesAllArrays() {\n    fillPool(pool, MAX_SIZE / ADAPTER.getElementSizeInBytes() + 1, 0);\n    pool.clearMemory();\n    assertEquals(0, pool.getCurrentSize());\n  }\n\n  @Test\n  public void testTrimMemoryUiHiddenOrLessRemovesHalfOfArrays() {\n    testTrimMemory(MAX_SIZE, TRIM_MEMORY_UI_HIDDEN, MAX_SIZE / 2);\n  }\n\n  @Test\n  public void testTrimMemoryRunningCriticalRemovesHalfOfBitmaps() {\n    testTrimMemory(MAX_SIZE, TRIM_MEMORY_RUNNING_CRITICAL, MAX_SIZE / 2);\n  }\n\n  @Test\n  public void testTrimMemoryUiHiddenOrLessRemovesNoArraysIfPoolLessThanHalfFull() {\n    testTrimMemory(MAX_SIZE / 2, TRIM_MEMORY_UI_HIDDEN, MAX_SIZE / 2);\n  }\n\n  @Test\n  public void testTrimMemoryBackgroundOrGreaterRemovesAllArrays() {\n    for (int trimLevel : new int[] {TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_COMPLETE}) {\n      testTrimMemory(MAX_SIZE, trimLevel, 0);\n    }\n  }\n\n  @Test\n  public void get_withEmptyPool_returnsExactArray() {\n    assertThat(pool.get(MAX_PUT_SIZE, byte[].class)).hasLength(MAX_PUT_SIZE);\n  }\n\n  @Test\n  public void get_withPoolContainingLargerArray_returnsLargerArray() {\n    byte[] expected = new byte[MAX_PUT_SIZE];\n    pool.put(expected);\n    assertThat(pool.get(MAX_PUT_SIZE - 1, byte[].class)).isSameInstanceAs(expected);\n  }\n\n  @Test\n  public void get_withPoolContainingSmallerArray_returnsExactArray() {\n    pool.put(new byte[MAX_PUT_SIZE - 1]);\n    assertThat(pool.get(MAX_PUT_SIZE, byte[].class)).hasLength(MAX_PUT_SIZE);\n  }\n\n  @Test\n  public void get_withPoolLessThanHalfFull_returnsFromPools() {\n    int size = MAX_SIZE / 2;\n    byte[] expected = new byte[size];\n    pool.put(expected);\n    assertThat(pool.get(1, byte[].class)).isSameInstanceAs(expected);\n  }\n\n  @Test\n  public void get_withPoolMoreThanHalfFull_sizeMoreThanHalfArrayInPool_returnsArray() {\n    Set<byte[]> expected = new HashSet<>();\n    for (int i = 0; i < 3; i++) {\n      byte[] toPut = new byte[MAX_SIZE / 3];\n      expected.add(toPut);\n      pool.put(toPut);\n    }\n    byte[] received = pool.get(2, byte[].class);\n    assertThat(expected).contains(received);\n  }\n\n  @Test\n  public void get_withPoolMoreThanHalfFull_sizeLessThanHalfArrayInPool_returnsNewArray() {\n    pool = new LruArrayPool(100);\n    for (int i = 0; i < 3; i++) {\n      byte[] toPut = new byte[100 / 3];\n      pool.put(toPut);\n    }\n    int requestedSize = 100 / 3 / LruArrayPool.MAX_OVER_SIZE_MULTIPLE;\n    byte[] received = pool.get(requestedSize, byte[].class);\n    assertThat(received).hasLength(requestedSize);\n  }\n\n  @Test\n  public void getExact_withEmptyPool_returnsExactArray() {\n    byte[] result = pool.getExact(MAX_PUT_SIZE, byte[].class);\n    assertThat(result).hasLength(MAX_PUT_SIZE);\n  }\n\n  @Test\n  public void getExact_withPoolContainingLargerArray_returnsExactArray() {\n    pool.put(new byte[MAX_PUT_SIZE]);\n    int expectedSize = MAX_PUT_SIZE - 1;\n    assertThat(pool.getExact(expectedSize, byte[].class)).hasLength(expectedSize);\n  }\n\n  @Test\n  public void getExact_withPoolContainingSmallerArray_returnsExactArray() {\n    pool.put(new byte[MAX_PUT_SIZE - 1]);\n    assertThat(pool.getExact(MAX_PUT_SIZE, byte[].class)).hasLength(MAX_PUT_SIZE);\n  }\n\n  @Test\n  public void getExact_withPoolContainingExactArray_returnsArray() {\n    byte[] expected = new byte[MAX_PUT_SIZE];\n    pool.put(expected);\n    assertThat(pool.getExact(MAX_PUT_SIZE, byte[].class)).isSameInstanceAs(expected);\n  }\n\n  @Test\n  public void put_withArrayMoreThanHalfPoolSize_doesNotRetainArray() {\n    int targetSize = (MAX_SIZE / 2) + 1;\n    byte[] toPut = new byte[targetSize];\n    pool.put(toPut);\n    assertThat(pool.getCurrentSize()).isEqualTo(0);\n    assertThat(pool.get(targetSize, byte[].class)).isNotSameInstanceAs(toPut);\n  }\n\n  private void testTrimMemory(int fillSize, int trimLevel, int expectedSize) {\n    pool = new LruArrayPool(MAX_SIZE);\n    fillPool(pool, fillSize / ADAPTER.getElementSizeInBytes(), 1);\n    pool.trimMemory(trimLevel);\n    assertEquals(\"Failed level=\" + trimLevel, expectedSize, pool.getCurrentSize());\n  }\n\n  private void fillPool(LruArrayPool pool, int arrayCount, int arrayLength) {\n    for (int i = 0; i < arrayCount; i++) {\n      pool.put(createArray(ARRAY_CLASS, arrayLength, 10));\n    }\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static <T> T createArray(Class<T> type, int size, int value) {\n    Object array = null;\n    if (type.equals(int[].class)) {\n      array = new int[size];\n      Arrays.fill((int[]) array, value);\n    } else if (type.equals(byte[].class)) {\n      array = new byte[size];\n      Arrays.fill((byte[]) array, (byte) value);\n    }\n    return (T) array;\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPoolTest.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;\nimport static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;\nimport static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;\nimport static android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = 28)\npublic class LruBitmapPoolTest {\n  private static final int MAX_SIZE = 10;\n  private static final Set<Bitmap.Config> ALLOWED_CONFIGS =\n      Collections.singleton(Bitmap.Config.ARGB_8888);\n  private MockStrategy strategy;\n  private LruBitmapPool pool;\n\n  @Before\n  public void setUp() throws Exception {\n    strategy = new MockStrategy();\n    pool = new LruBitmapPool(MAX_SIZE, strategy, ALLOWED_CONFIGS);\n  }\n\n  @Test\n  public void testICanAddAndGetABitmap() {\n    fillPool(pool, 1);\n    pool.put(createMutableBitmap());\n    assertNotNull(pool.get(100, 100, Bitmap.Config.ARGB_8888));\n  }\n\n  @Test\n  public void testImmutableBitmapsAreNotAdded() {\n    Bitmap bitmap = createMutableBitmap();\n    Bitmap immutable = bitmap.copy(Bitmap.Config.ARGB_8888, /* isMutable= */ false);\n    assertThat(immutable.isMutable()).isFalse();\n    pool.put(immutable);\n    assertThat(strategy.bitmaps).isEmpty();\n  }\n\n  @Test\n  public void testItIsSizeLimited() {\n    fillPool(pool, MAX_SIZE + 2);\n    assertEquals(2, strategy.numRemoves);\n  }\n\n  @Test\n  public void testBitmapLargerThanPoolIsNotAdded() {\n    strategy =\n        new MockStrategy() {\n          @Override\n          public int getSize(Bitmap bitmap) {\n            return 4;\n          }\n        };\n    pool = new LruBitmapPool(3, strategy, ALLOWED_CONFIGS);\n    pool.put(createMutableBitmap());\n    assertEquals(0, strategy.numRemoves);\n    assertEquals(0, strategy.numPuts);\n  }\n\n  @Test\n  public void testClearMemoryRemovesAllBitmaps() {\n    fillPool(pool, MAX_SIZE);\n    pool.clearMemory();\n\n    assertEquals(MAX_SIZE, strategy.numRemoves);\n  }\n\n  @Test\n  public void testEvictedBitmapsAreRecycled() {\n    fillPool(pool, MAX_SIZE);\n    List<Bitmap> bitmaps = new ArrayList<>(MAX_SIZE);\n    bitmaps.addAll(strategy.bitmaps);\n\n    pool.clearMemory();\n\n    for (Bitmap b : bitmaps) {\n      assertTrue(b.isRecycled());\n    }\n  }\n\n  @Config(sdk = Build.VERSION_CODES.KITKAT)\n  @Test\n  public void testTrimMemoryUiHiddenOrLessRemovesHalfOfBitmaps_preM() {\n    testTrimMemory(MAX_SIZE, TRIM_MEMORY_UI_HIDDEN, MAX_SIZE / 2);\n  }\n\n  @Config(sdk = Build.VERSION_CODES.M)\n  @Test\n  public void testTrimMemoryUiHiddenOrLessRemovesHalfOfBitmaps_postM() {\n    testTrimMemory(MAX_SIZE, TRIM_MEMORY_UI_HIDDEN, 0);\n  }\n\n  @Test\n  public void testTrimMemoryRunningCriticalRemovesHalfOfBitmaps() {\n    testTrimMemory(MAX_SIZE, TRIM_MEMORY_RUNNING_CRITICAL, MAX_SIZE / 2);\n  }\n\n  @Test\n  public void testTrimMemoryRunningCriticalOrLessRemovesNoBitmapsIfPoolLessThanHalfFull() {\n    testTrimMemory(MAX_SIZE / 2, TRIM_MEMORY_RUNNING_CRITICAL, MAX_SIZE / 2);\n  }\n\n  @Test\n  public void testTrimMemoryBackgroundOrGreaterRemovesAllBitmaps() {\n    for (int trimLevel : new int[] {TRIM_MEMORY_BACKGROUND, TRIM_MEMORY_COMPLETE}) {\n      testTrimMemory(MAX_SIZE, trimLevel, 0);\n    }\n  }\n\n  @Test\n  public void testPassesArgb888ToStrategyAsConfigForRequestsWithNullConfigsOnGet() {\n    LruPoolStrategy strategy = mock(LruPoolStrategy.class);\n    LruBitmapPool pool = new LruBitmapPool(100, strategy, ALLOWED_CONFIGS);\n\n    Bitmap expected = createMutableBitmap();\n    when(strategy.get(anyInt(), anyInt(), eq(Bitmap.Config.ARGB_8888))).thenReturn(expected);\n    Bitmap result = pool.get(100, 100, null);\n\n    assertEquals(expected, result);\n  }\n\n  @Test\n  public void testPassesArgb8888ToStrategyAsConfigForRequestsWithNullConfigsOnGetDirty() {\n    LruPoolStrategy strategy = mock(LruPoolStrategy.class);\n    LruBitmapPool pool = new LruBitmapPool(100, strategy, ALLOWED_CONFIGS);\n\n    Bitmap expected = createMutableBitmap();\n    when(strategy.get(anyInt(), anyInt(), eq(Bitmap.Config.ARGB_8888))).thenReturn(expected);\n    Bitmap result = pool.getDirty(100, 100, null);\n\n    assertEquals(expected, result);\n  }\n\n  @Test\n  public void get_withNullConfig_andEmptyPool_returnsNewArgb8888Bitmap() {\n    Bitmap result = pool.get(100, 100, /* config= */ null);\n    assertThat(result.getConfig()).isEqualTo(Bitmap.Config.ARGB_8888);\n  }\n\n  @Test\n  public void getDirty_withNullConfig_andEmptyPool_returnsNewArgb8888Bitmap() {\n    Bitmap result = pool.getDirty(100, 100, /* config= */ null);\n    assertThat(result.getConfig()).isEqualTo(Bitmap.Config.ARGB_8888);\n  }\n\n  private void testTrimMemory(int fillSize, int trimLevel, int expectedSize) {\n    MockStrategy strategy = new MockStrategy();\n    LruBitmapPool pool = new LruBitmapPool(MAX_SIZE, strategy, ALLOWED_CONFIGS);\n    fillPool(pool, fillSize);\n    pool.trimMemory(trimLevel);\n    assertEquals(\"Failed level=\" + trimLevel, expectedSize, strategy.bitmaps.size());\n  }\n\n  @Test\n  public void testCanIncreaseSizeDynamically() {\n    int sizeMultiplier = 2;\n    pool.setSizeMultiplier(2);\n    fillPool(pool, MAX_SIZE * sizeMultiplier);\n\n    assertEquals(0, strategy.numRemoves);\n  }\n\n  @Test\n  public void testCanDecreaseSizeDynamically() {\n    fillPool(pool, MAX_SIZE);\n    assertEquals(0, strategy.numRemoves);\n\n    float sizeMultiplier = 0.5f;\n    pool.setSizeMultiplier(sizeMultiplier);\n\n    assertEquals(Math.round(MAX_SIZE * sizeMultiplier), strategy.numRemoves);\n  }\n\n  @Test\n  public void testCanResetSizeDynamically() {\n    int sizeMultiplier = 2;\n    pool.setSizeMultiplier(sizeMultiplier);\n    fillPool(pool, MAX_SIZE * sizeMultiplier);\n\n    pool.setSizeMultiplier(1);\n\n    assertEquals(MAX_SIZE * sizeMultiplier - MAX_SIZE, strategy.numRemoves);\n  }\n\n  @Test\n  public void testCanGetCurrentMaxSize() {\n    assertEquals(MAX_SIZE, pool.getMaxSize());\n  }\n\n  @Test\n  public void testMaxSizeChangesAfterSizeMultiplier() {\n    pool.setSizeMultiplier(2);\n    assertEquals(2 * MAX_SIZE, pool.getMaxSize());\n  }\n\n  @Test\n  public void testBitmapsWithDisallowedConfigsAreIgnored() {\n    pool = new LruBitmapPool(100, strategy, Collections.singleton(Bitmap.Config.ARGB_4444));\n\n    Bitmap bitmap = createMutableBitmap(Bitmap.Config.RGB_565);\n    pool.put(bitmap);\n\n    assertEquals(0, strategy.numPuts);\n  }\n\n  @Test\n  @Config(sdk = 19)\n  public void testBitmapsWithAllowedNullConfigsAreAllowed() {\n    pool = new LruBitmapPool(100, strategy, Collections.<Bitmap.Config>singleton(null));\n\n    Bitmap bitmap = createMutableBitmap();\n    bitmap.setConfig(null);\n\n    pool.put(bitmap);\n\n    assertEquals(1, strategy.numPuts);\n  }\n\n  private void fillPool(LruBitmapPool pool, int fillCount) {\n    for (int i = 0; i < fillCount; i++) {\n      pool.put(createMutableBitmap());\n    }\n  }\n\n  private Bitmap createMutableBitmap() {\n    return createMutableBitmap(Bitmap.Config.ARGB_8888);\n  }\n\n  private Bitmap createMutableBitmap(Bitmap.Config config) {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, config);\n    assertThat(bitmap.isMutable()).isTrue();\n    return bitmap;\n  }\n\n  private static class MockStrategy implements LruPoolStrategy {\n    private final ArrayDeque<Bitmap> bitmaps = new ArrayDeque<>();\n    private int numRemoves;\n    private int numPuts;\n\n    @Override\n    public void put(Bitmap bitmap) {\n      numPuts++;\n      bitmaps.add(bitmap);\n    }\n\n    @Override\n    public Bitmap get(int width, int height, Bitmap.Config config) {\n      return bitmaps.isEmpty() ? null : bitmaps.removeLast();\n    }\n\n    @Override\n    public Bitmap removeLast() {\n      numRemoves++;\n      return bitmaps.removeLast();\n    }\n\n    @Override\n    public String logBitmap(Bitmap bitmap) {\n      return null;\n    }\n\n    @Override\n    public String logBitmap(int width, int height, Bitmap.Config config) {\n      return null;\n    }\n\n    @Override\n    public int getSize(Bitmap bitmap) {\n      return 1;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/bitmap_recycle/SizeConfigStrategyTest.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport android.graphics.Bitmap;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.google.common.testing.EqualsTester;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(AndroidJUnit4.class)\npublic class SizeConfigStrategyTest {\n\n  @Mock private SizeConfigStrategy.KeyPool pool;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n  }\n\n  @Test\n  public void testKeyEquals() {\n    new EqualsTester()\n        .addEqualityGroup(\n            new SizeConfigStrategy.Key(pool, 100, Bitmap.Config.ARGB_8888),\n            new SizeConfigStrategy.Key(pool, 100, Bitmap.Config.ARGB_8888))\n        .addEqualityGroup(new SizeConfigStrategy.Key(pool, 101, Bitmap.Config.ARGB_8888))\n        .addEqualityGroup(new SizeConfigStrategy.Key(pool, 100, Bitmap.Config.RGB_565))\n        .addEqualityGroup(new SizeConfigStrategy.Key(pool, 100, null /*config*/))\n        .testEquals();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategyKeyTest.java",
    "content": "package com.bumptech.glide.load.engine.bitmap_recycle;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotEquals;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport com.bumptech.glide.load.engine.bitmap_recycle.SizeStrategy.Key;\nimport com.google.common.testing.EqualsTester;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class SizeStrategyKeyTest {\n\n  private SizeStrategy.KeyPool keyPool;\n\n  @Before\n  public void setUp() {\n    keyPool = mock(SizeStrategy.KeyPool.class);\n  }\n\n  @Test\n  public void testEquality() {\n    Key first = new Key(keyPool);\n    first.init(100);\n    Key second = new Key(keyPool);\n    second.init(100);\n    Key third = new Key(keyPool);\n    third.init(50);\n\n    new EqualsTester().addEqualityGroup(first, second).addEqualityGroup(third).testEquals();\n  }\n\n  @Test\n  public void testReturnsSelfToPoolOnOffer() {\n    Key key = new Key(keyPool);\n    key.offer();\n\n    verify(keyPool).offer(eq(key));\n  }\n\n  @Test\n  public void testInitSetsSize() {\n    Key key = new Key(keyPool);\n    key.init(100);\n\n    Key other = new Key(keyPool);\n    other.init(200);\n\n    assertNotEquals(key, other);\n\n    key.init(200);\n\n    assertEquals(key, other);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/cache/DiskLruCacheWrapperTest.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertArrayEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.fail;\nimport static org.junit.Assume.assumeTrue;\nimport static org.mockito.Mockito.mock;\n\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.bumptech.glide.tests.Util;\nimport java.io.File;\nimport java.io.IOException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class DiskLruCacheWrapperTest {\n  private DiskCache cache;\n  private byte[] data;\n  private ObjectKey key;\n  private File dir;\n\n  @Before\n  public void setUp() {\n    dir = ApplicationProvider.getApplicationContext().getCacheDir();\n    cache = DiskLruCacheWrapper.create(dir, 10 * 1024 * 1024);\n    key = new ObjectKey(\"test\" + Math.random());\n    data = new byte[] {1, 2, 3, 4, 5, 6};\n  }\n\n  @After\n  public void tearDown() {\n    try {\n      cache.clear();\n    } finally {\n      deleteRecursive(dir);\n    }\n  }\n\n  private static void deleteRecursive(File file) {\n    if (file.isDirectory()) {\n      File[] files = file.listFiles();\n      if (files != null) {\n        for (File f : files) {\n          deleteRecursive(f);\n        }\n      }\n    }\n    // GC before delete() to release files on Windows (https://stackoverflow.com/a/4213208/253468)\n    System.gc();\n    if (!file.delete() && file.exists()) {\n      throw new RuntimeException(\"Failed to delete: \" + file);\n    }\n  }\n\n  @Test\n  public void testCanInsertAndGet() throws IOException {\n    cache.put(\n        key,\n        new DiskCache.Writer() {\n          @Override\n          public boolean write(@NonNull File file) {\n            try {\n              Util.writeFile(file, data);\n            } catch (IOException e) {\n              fail(e.toString());\n            }\n            return true;\n          }\n        });\n\n    byte[] received = Util.readFile(cache.get(key), data.length);\n\n    assertArrayEquals(data, received);\n  }\n\n  @Test\n  public void testDoesNotCommitIfWriterReturnsFalse() {\n    cache.put(\n        key,\n        new DiskCache.Writer() {\n          @Override\n          public boolean write(@NonNull File file) {\n            return false;\n          }\n        });\n\n    assertNull(cache.get(key));\n  }\n\n  @Test\n  public void testDoesNotCommitIfWriterWritesButReturnsFalse() {\n    cache.put(\n        key,\n        new DiskCache.Writer() {\n          @Override\n          public boolean write(@NonNull File file) {\n            try {\n              Util.writeFile(file, data);\n            } catch (IOException e) {\n              fail(e.toString());\n            }\n            return false;\n          }\n        });\n\n    assertNull(cache.get(key));\n  }\n\n  @Test\n  public void testEditIsAbortedIfWriterThrows() throws IOException {\n    try {\n      cache.put(\n          key,\n          new DiskCache.Writer() {\n            @Override\n            public boolean write(@NonNull File file) {\n              throw new RuntimeException(\"test\");\n            }\n          });\n    } catch (RuntimeException e) {\n      // Expected.\n    }\n\n    cache.put(\n        key,\n        new DiskCache.Writer() {\n          @Override\n          public boolean write(@NonNull File file) {\n            try {\n              Util.writeFile(file, data);\n            } catch (IOException e) {\n              fail(e.toString());\n            }\n            return true;\n          }\n        });\n\n    byte[] received = Util.readFile(cache.get(key), data.length);\n\n    assertArrayEquals(data, received);\n  }\n\n  // Tests #2465.\n  @Test\n  public void clearDiskCache_afterOpeningDiskCache_andDeleteDirectoryOutsideGlide_doesNotThrow() {\n    assumeTrue(\"A file handle is likely open, so cannot delete dir\", !Util.isWindows());\n    DiskCache cache = DiskLruCacheWrapper.create(dir, 1024 * 1024);\n    cache.get(mock(Key.class));\n    deleteRecursive(dir);\n    cache.clear();\n  }\n\n  // Tests #2465.\n  @Test\n  public void get_afterDeleteDirectoryOutsideGlideAndClose_doesNotThrow() {\n    assumeTrue(\"A file handle is likely open, so cannot delete dir\", !Util.isWindows());\n    DiskCache cache = DiskLruCacheWrapper.create(dir, 1024 * 1024);\n    cache.get(mock(Key.class));\n    deleteRecursive(dir);\n    cache.clear();\n\n    cache.get(mock(Key.class));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/cache/LruCacheTest.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.AdditionalMatchers.not;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.util.LruCache;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class LruCacheTest {\n  private static final int SIZE = 10;\n  private LruCache<String, Object> cache;\n  private CacheListener listener;\n  private String currentKey;\n\n  @Before\n  public void setUp() {\n    currentKey = \"\";\n    listener = mock(CacheListener.class);\n    cache = new TestLruCache(SIZE, listener);\n    when(listener.getSize(any())).thenReturn(1);\n  }\n\n  @Test\n  public void testCanAddAndRetrieveItem() {\n    String key = getKey();\n    Object object = new Object();\n\n    cache.put(key, object);\n\n    assertEquals(object, cache.get(key));\n  }\n\n  @Test\n  public void testCanPutNullItemWithoutChangingSize() {\n    String key = getKey();\n    cache.put(key, null);\n\n    for (int i = 0; i < SIZE; i++) {\n      cache.put(getKey(), new Object());\n    }\n\n    verify(listener, never()).onItemRemoved(any());\n  }\n\n  @Test\n  public void testReplacingNonNullItemWithNullItemDecreasesSize() {\n    String key = getKey();\n    Object initialValue = new Object();\n    cache.put(key, initialValue);\n    cache.put(key, null);\n\n    for (int i = 0; i < SIZE; i++) {\n      cache.put(getKey(), new Object());\n    }\n\n    verify(listener).onItemRemoved(initialValue);\n  }\n\n  @Test\n  public void testReplacingNullItemWIthNullItemIncreasesSize() {\n    String key = getKey();\n    cache.put(key, null);\n    cache.put(key, new Object());\n\n    for (int i = 0; i < SIZE; i++) {\n      cache.put(getKey(), new Object());\n    }\n\n    verify(listener).onItemRemoved(any());\n  }\n\n  @Test\n  public void testReplacingNonNullItemWithNonNullItemUpdatesSize() {\n    String key = getKey();\n    Object initialValue = new Object();\n    cache.put(key, initialValue);\n    cache.put(key, new Object());\n\n    for (int i = 0; i < SIZE - 1; i++) {\n      cache.put(getKey(), new Object());\n    }\n\n    verify(listener).onItemRemoved(initialValue);\n    verify(listener, never()).onItemRemoved(not(eq(initialValue)));\n  }\n\n  @Test\n  public void testCacheContainsAddedBitmap() {\n    final String key = getKey();\n    cache.put(key, new Object());\n    assertTrue(cache.contains(key));\n  }\n\n  @Test\n  public void testEmptyCacheDoesNotContainKey() {\n    assertFalse(cache.contains(getKey()));\n  }\n\n  @Test\n  public void testItIsSizeLimited() {\n    for (int i = 0; i < SIZE; i++) {\n      cache.put(getKey(), new Object());\n    }\n    verify(listener, never()).onItemRemoved(any());\n    cache.put(getKey(), new Object());\n    verify(listener).onItemRemoved(any());\n  }\n\n  @Test\n  public void testLeastRecentlyAddKeyEvictedFirstIfGetsAreEqual() {\n    Object first = new Object();\n    cache.put(getKey(), first);\n    for (int i = 0; i < SIZE; i++) {\n      cache.put(getKey(), new Object());\n    }\n\n    verify(listener).onItemRemoved(eq(first));\n    verify(listener, times(1)).onItemRemoved(any(Object.class));\n  }\n\n  @Test\n  public void testLeastRecentlyUsedKeyEvictedFirst() {\n    String mostRecentlyUsedKey = getKey();\n    Object mostRecentlyUsedObject = new Object();\n    String leastRecentlyUsedKey = getKey();\n    Object leastRecentlyUsedObject = new Object();\n\n    cache.put(mostRecentlyUsedKey, mostRecentlyUsedObject);\n    cache.put(leastRecentlyUsedKey, leastRecentlyUsedObject);\n\n    cache.get(mostRecentlyUsedKey);\n    for (int i = 0; i < SIZE - 1; i++) {\n      cache.put(getKey(), new Object());\n    }\n\n    verify(listener).onItemRemoved(eq(leastRecentlyUsedObject));\n    verify(listener, times(1)).onItemRemoved(any(Object.class));\n  }\n\n  @Test\n  public void testItemLargerThanCacheIsImmediatelyEvicted() {\n    Object tooLarge = new Object();\n    when(listener.getSize(eq(tooLarge))).thenReturn(SIZE + 1);\n    cache.put(getKey(), tooLarge);\n\n    verify(listener).onItemRemoved(eq(tooLarge));\n  }\n\n  @Test\n  public void testItemLargerThanCacheDoesNotCauseAdditionalEvictions() {\n    cache.put(getKey(), new Object());\n\n    Object tooLarge = new Object();\n    when(listener.getSize(eq(tooLarge))).thenReturn(SIZE + 1);\n\n    cache.put(getKey(), tooLarge);\n\n    verify(listener, times(1)).onItemRemoved(any());\n  }\n\n  @Test\n  public void testClearMemoryRemovesAllItems() {\n    String first = getKey();\n    String second = getKey();\n    cache.put(first, new Object());\n    cache.put(second, new Object());\n\n    cache.clearMemory();\n\n    assertFalse(cache.contains(first));\n    assertFalse(cache.contains(second));\n  }\n\n  @Test\n  public void testCanPutSameItemMultipleTimes() {\n    String key = getKey();\n    Object value = new Object();\n    for (int i = 0; i < SIZE * 2; i++) {\n      cache.put(key, value);\n    }\n\n    verify(listener, never()).onItemRemoved(any());\n  }\n\n  @Test\n  public void put_withSameKeyAndValueTwice_doesNotEvictItems() {\n    String key = getKey();\n    Object value = new Object();\n    cache.put(key, value);\n    cache.put(key, value);\n\n    verify(listener, never()).onItemRemoved(any());\n  }\n\n  @Test\n  public void put_withExistingNullValue_doesNotNotifyListener() {\n    String key = getKey();\n    cache.put(key, /* item= */ null);\n    cache.put(key, new Object());\n\n    verify(listener, never()).onItemRemoved(any());\n  }\n\n  @Test\n  public void put_withNullValue_withSizeGreaterThanMaximum_notifiesListener() {\n    String key = getKey();\n    when(listener.getSize(null)).thenReturn((int) (cache.getMaxSize() * 2));\n    cache.put(key, null);\n\n    verify(listener).onItemRemoved(any());\n  }\n\n  @Test\n  public void testCanIncreaseSizeDynamically() {\n    int sizeMultiplier = 2;\n    cache.setSizeMultiplier(sizeMultiplier);\n    for (int i = 0; i < SIZE * sizeMultiplier; i++) {\n      cache.put(getKey(), new Object());\n    }\n\n    verify(listener, never()).onItemRemoved(any());\n  }\n\n  @Test\n  public void testCanDecreaseSizeDynamically() {\n    for (int i = 0; i < SIZE; i++) {\n      cache.put(getKey(), new Object());\n    }\n    verify(listener, never()).onItemRemoved(any());\n\n    float smallerMultiplier = 0.4f;\n\n    cache.setSizeMultiplier(smallerMultiplier);\n\n    verify(listener, times((int) (SIZE * (1 - smallerMultiplier)))).onItemRemoved(any());\n  }\n\n  @Test\n  public void testCanResetSizeDynamically() {\n    int sizeMultiplier = 2;\n    cache.setSizeMultiplier(sizeMultiplier);\n    for (int i = 0; i < SIZE * sizeMultiplier; i++) {\n      cache.put(getKey(), new Object());\n    }\n\n    cache.setSizeMultiplier(1);\n\n    verify(listener, times((sizeMultiplier * SIZE) - SIZE)).onItemRemoved(any());\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfMultiplierLessThanZero() {\n    cache.setSizeMultiplier(-1);\n  }\n\n  @Test\n  public void testCanHandleZeroAsMultiplier() {\n    for (int i = 0; i < SIZE; i++) {\n      cache.put(getKey(), new Object());\n    }\n    cache.setSizeMultiplier(0);\n\n    verify(listener, times(SIZE)).onItemRemoved(any());\n  }\n\n  @Test\n  public void testCanRemoveKeys() {\n    String key = getKey();\n    Object value = new Object();\n    cache.put(key, value);\n    cache.remove(key);\n\n    assertNull(cache.get(key));\n    assertFalse(cache.contains(key));\n  }\n\n  @Test\n  public void testDecreasesSizeWhenRemovesKey() {\n    String key = getKey();\n    Object value = new Object();\n    cache.put(key, value);\n    for (int i = 0; i < SIZE - 1; i++) {\n      cache.put(getKey(), value);\n    }\n    cache.remove(key);\n    cache.put(key, value);\n\n    verify(listener, never()).onItemRemoved(any());\n  }\n\n  @Test\n  public void testDoesNotCallListenerWhenRemovesKey() {\n    String key = getKey();\n    cache.put(key, new Object());\n    cache.remove(key);\n\n    verify(listener, never()).onItemRemoved(any());\n  }\n\n  @Test\n  public void testGetMaxSizeReturnsCurrentMaxSizeOfCache() {\n    assertEquals(SIZE, cache.getMaxSize());\n  }\n\n  @Test\n  public void setSizeMultiplier_withItemWhoseSizeDecreasesAfterAdd_doesNotCrash() {\n    Object itemWhoseSizeWillChange = new Object();\n    when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn(SIZE / 2);\n    cache.put(getKey(), itemWhoseSizeWillChange);\n    cache.setSizeMultiplier(0);\n  }\n\n  @Test\n  public void getCurrentSize_afterRemovingItemWhoseSizeChanged_returnsZero() {\n    Object itemWhoseSizeWillChange = new Object();\n    when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn(SIZE / 2);\n    String key = getKey();\n    cache.put(key, itemWhoseSizeWillChange);\n    cache.remove(key);\n\n    assertThat(cache.getCurrentSize()).isEqualTo(0);\n  }\n\n  @Test\n  public void clearMemory_afterRemovingItemWhoseSizeChanged_doesNotCrash() {\n    Object itemWhoseSizeWillChange = new Object();\n    when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn((SIZE / 2) - 1);\n    String key = getKey();\n    cache.put(key, itemWhoseSizeWillChange);\n    cache.remove(key);\n\n    cache.clearMemory();\n  }\n\n  @Test\n  public void getCurrentSize_afterUpdatingItemWhoseSizeChanged_returnsTheNewSize() {\n    Object itemWhoseSizeWillChange = new Object();\n    when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn((SIZE / 2) - 1);\n    String key = getKey();\n    cache.put(key, itemWhoseSizeWillChange);\n    cache.put(key, new Object());\n\n    assertThat(cache.getCurrentSize()).isEqualTo(1);\n  }\n\n  @Test\n  public void clearMemory_afterUpdatingItemWhoseSizeChanged_doesNotCrash() {\n    Object itemWhoseSizeWillChange = new Object();\n    when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn((SIZE / 2) - 1);\n    String key = getKey();\n    cache.put(key, itemWhoseSizeWillChange);\n    cache.put(key, new Object());\n\n    cache.clearMemory();\n  }\n\n  @Test\n  public void testGetMaxSizeChangesIfMaxSizeChanges() {\n    int multiplier = 2;\n    cache.setSizeMultiplier(multiplier);\n\n    assertEquals(SIZE * multiplier, cache.getMaxSize());\n  }\n\n  @Test\n  public void getCurrentSizeReturnsZeroForEmptyCache() {\n    assertEquals(0, cache.getCurrentSize());\n  }\n\n  @Test\n  public void testGetCurrentSizeIncreasesAsSizeIncreases() {\n    cache.put(getKey(), new Object());\n    assertEquals(1, cache.getCurrentSize());\n    cache.put(getKey(), new Object());\n    assertEquals(2, cache.getCurrentSize());\n  }\n\n  @Test\n  public void testGetCurrentSizeDoesNotChangeWhenSizeMultiplierChangesIfNoItemsAreEvicted() {\n    cache.put(getKey(), new Object());\n    assertEquals(1, cache.getCurrentSize());\n    cache.setSizeMultiplier(2);\n    assertEquals(1, cache.getCurrentSize());\n  }\n\n  @Test\n  public void testGetCurrentSizeChangesIfItemsAreEvictedWhenSizeMultiplierChanges() {\n    for (int i = 0; i < SIZE; i++) {\n      cache.put(getKey(), new Object());\n    }\n    assertEquals(SIZE, cache.getCurrentSize());\n    cache.setSizeMultiplier(0.5f);\n    assertEquals(SIZE / 2, cache.getCurrentSize());\n  }\n\n  private String getKey() {\n    currentKey += \"1\";\n    return currentKey;\n  }\n\n  private interface CacheListener {\n    void onItemRemoved(Object item);\n\n    int getSize(Object item);\n  }\n\n  private static class TestLruCache extends LruCache<String, Object> {\n    private final CacheListener listener;\n\n    TestLruCache(int size, CacheListener listener) {\n      super(size);\n      this.listener = listener;\n    }\n\n    @Override\n    protected void onItemEvicted(@NonNull String key, @Nullable Object item) {\n      listener.onItemRemoved(item);\n    }\n\n    @Override\n    protected int getSize(@Nullable Object item) {\n      return listener.getSize(item);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/cache/LruResourceCacheTest.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport static com.bumptech.glide.load.engine.cache.MemoryCache.ResourceRemovedListener;\nimport static com.bumptech.glide.tests.Util.anyResource;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.ComponentCallbacks2;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.LruCache;\nimport java.security.MessageDigest;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class LruResourceCacheTest {\n\n  @Test\n  public void put_withExistingItem_updatesSizeCorrectly() {\n    PutWithExistingEntryHarness harness = new PutWithExistingEntryHarness();\n    harness.cache.put(harness.key, harness.first);\n    harness.cache.put(harness.key, harness.second);\n\n    assertThat(harness.cache.getCurrentSize()).isEqualTo(harness.second.getSize());\n  }\n\n  @Test\n  public void put_withExistingItem_evictsExistingItem() {\n    PutWithExistingEntryHarness harness = new PutWithExistingEntryHarness();\n    harness.cache.put(harness.key, harness.first);\n    harness.cache.put(harness.key, harness.second);\n\n    verify(harness.listener).onResourceRemoved(harness.first);\n  }\n\n  @Test\n  public void get_afterPutWithExistingItem_returnsNewItem() {\n    PutWithExistingEntryHarness harness = new PutWithExistingEntryHarness();\n    harness.cache.put(harness.key, harness.first);\n    harness.cache.put(harness.key, harness.second);\n\n    assertThat(harness.cache.get(harness.key)).isEqualTo(harness.second);\n  }\n\n  @Test\n  public void onItemEvicted_withNullValue_doesNotNotifyListener() {\n    PutWithExistingEntryHarness harness = new PutWithExistingEntryHarness();\n    harness.cache.onItemEvicted(new MockKey(), null);\n    verify(harness.listener, never()).onResourceRemoved(anyResource());\n  }\n\n  @Test\n  public void clearMemory_afterPutWithExistingItem_evictsOnlyNewItem() {\n    PutWithExistingEntryHarness harness = new PutWithExistingEntryHarness();\n    harness.cache.put(harness.key, harness.first);\n    harness.cache.put(harness.key, harness.second);\n\n    verify(harness.listener).onResourceRemoved(harness.first);\n    verify(harness.listener, never()).onResourceRemoved(harness.second);\n\n    harness.cache.clearMemory();\n\n    verify(harness.listener, times(1)).onResourceRemoved(harness.first);\n    verify(harness.listener).onResourceRemoved(harness.second);\n  }\n\n  @Test\n  public void testTrimMemoryBackground() {\n    TrimClearMemoryCacheHarness harness = new TrimClearMemoryCacheHarness();\n\n    harness.resourceCache.trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);\n\n    verify(harness.listener).onResourceRemoved(eq(harness.first));\n    verify(harness.listener).onResourceRemoved(eq(harness.second));\n  }\n\n  @Test\n  public void testTrimMemoryModerate() {\n    TrimClearMemoryCacheHarness harness = new TrimClearMemoryCacheHarness();\n\n    harness.resourceCache.trimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE);\n\n    verify(harness.listener).onResourceRemoved(harness.first);\n    verify(harness.listener).onResourceRemoved(harness.second);\n  }\n\n  @Test\n  public void testTrimMemoryUiHidden() {\n    TrimClearMemoryCacheHarness harness = new TrimClearMemoryCacheHarness();\n\n    harness.resourceCache.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);\n\n    verify(harness.listener).onResourceRemoved(harness.first);\n    verify(harness.listener, never()).onResourceRemoved(harness.second);\n  }\n\n  @Test\n  public void testTrimMemoryRunningCritical() {\n    TrimClearMemoryCacheHarness harness = new TrimClearMemoryCacheHarness();\n\n    harness.resourceCache.trimMemory(ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL);\n\n    verify(harness.listener).onResourceRemoved(harness.first);\n    verify(harness.listener, never()).onResourceRemoved(harness.second);\n  }\n\n  @Test\n  public void testResourceRemovedListenerIsNotifiedWhenResourceIsRemoved() {\n    LruResourceCache resourceCache = new LruResourceCache(100);\n    Resource<?> resource = mockResource();\n    when(resource.getSize()).thenReturn(200);\n\n    ResourceRemovedListener listener = mock(ResourceRemovedListener.class);\n\n    resourceCache.setResourceRemovedListener(listener);\n    resourceCache.put(new MockKey(), resource);\n\n    verify(listener).onResourceRemoved(eq(resource));\n  }\n\n  @Test\n  public void testSizeIsBasedOnResource() {\n    LruResourceCache resourceCache = new LruResourceCache(100);\n    Resource<?> first = getResource(50);\n    MockKey firstKey = new MockKey();\n    resourceCache.put(firstKey, first);\n    Resource<?> second = getResource(50);\n    MockKey secondKey = new MockKey();\n    resourceCache.put(secondKey, second);\n\n    assertTrue(resourceCache.contains(firstKey));\n    assertTrue(resourceCache.contains(secondKey));\n\n    Resource<?> third = getResource(50);\n    MockKey thirdKey = new MockKey();\n    resourceCache.put(thirdKey, third);\n\n    assertFalse(resourceCache.contains(firstKey));\n    assertTrue(resourceCache.contains(secondKey));\n    assertTrue(resourceCache.contains(thirdKey));\n  }\n\n  @Test\n  public void testPreventEviction() {\n    final MemoryCache cache = new LruResourceCache(100);\n    final Resource<?> first = getResource(30);\n    final Key firstKey = new MockKey();\n    cache.put(firstKey, first);\n    Resource<?> second = getResource(30);\n    Key secondKey = new MockKey();\n    cache.put(secondKey, second);\n    Resource<?> third = getResource(30);\n    Key thirdKey = new MockKey();\n    cache.put(thirdKey, third);\n    cache.setResourceRemovedListener(\n        new ResourceRemovedListener() {\n          @Override\n          public void onResourceRemoved(@NonNull Resource<?> removed) {\n            if (removed == first) {\n              cache.put(firstKey, first);\n            }\n          }\n        });\n\n    // trims from 100 to 50, having 30+30+30 items, it should trim to 1 item\n    cache.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);\n\n    // and that 1 item must be first, because it's forced to return to cache in the listener\n    @SuppressWarnings(\"unchecked\")\n    LruCache<Key, Resource<?>> lruCache = (LruCache<Key, Resource<?>>) cache;\n    assertTrue(lruCache.contains(firstKey));\n    assertFalse(lruCache.contains(secondKey));\n    assertFalse(lruCache.contains(thirdKey));\n  }\n\n  private Resource<?> getResource(int size) {\n    Resource<?> resource = mockResource();\n    when(resource.getSize()).thenReturn(size);\n    return resource;\n  }\n\n  private static class MockKey implements Key {\n    @Override\n    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n      messageDigest.update(toString().getBytes(CHARSET));\n    }\n  }\n\n  private static class PutWithExistingEntryHarness {\n    final LruResourceCache cache = new LruResourceCache(100);\n    final Resource<?> first = mockResource();\n    final Resource<?> second = mockResource();\n    final ResourceRemovedListener listener = mock(ResourceRemovedListener.class);\n    final Key key = new MockKey();\n\n    PutWithExistingEntryHarness() {\n      when(first.getSize()).thenReturn(50);\n      when(second.getSize()).thenReturn(50);\n      cache.setResourceRemovedListener(listener);\n    }\n  }\n\n  private static class TrimClearMemoryCacheHarness {\n    final LruResourceCache resourceCache = new LruResourceCache(100);\n    final Resource<?> first = mockResource();\n    final Resource<?> second = mockResource();\n    final ResourceRemovedListener listener = mock(ResourceRemovedListener.class);\n\n    TrimClearMemoryCacheHarness() {\n      when(first.getSize()).thenReturn(50);\n      when(second.getSize()).thenReturn(50);\n      resourceCache.put(new MockKey(), first);\n      resourceCache.put(new MockKey(), second);\n      resourceCache.setResourceRemovedListener(listener);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/cache/MemorySizeCalculatorTest.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.os.Build;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.engine.cache.MemorySizeCalculatorTest.LowRamActivityManager;\nimport com.bumptech.glide.tests.Util;\nimport com.google.common.collect.Range;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\nimport org.robolectric.shadow.api.Shadow;\nimport org.robolectric.shadows.ShadowActivityManager;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = 19, shadows = LowRamActivityManager.class)\npublic class MemorySizeCalculatorTest {\n  private MemorySizeHarness harness;\n  private int initialSdkVersion;\n\n  @Before\n  public void setUp() {\n    initialSdkVersion = Build.VERSION.SDK_INT;\n    harness = new MemorySizeHarness();\n  }\n\n  @After\n  public void tearDown() {\n    Util.setSdkVersionInt(initialSdkVersion);\n  }\n\n  @Test\n  public void testDefaultMemoryCacheSizeIsTwiceScreenSize() {\n    Shadows.shadowOf(harness.activityManager).setMemoryClass(getLargeEnoughMemoryClass());\n\n    float memoryCacheSize = harness.getCalculator().getMemoryCacheSize();\n\n    assertThat(memoryCacheSize).isEqualTo(harness.getScreenSize() * harness.memoryCacheScreens);\n  }\n\n  @Test\n  public void testCanSetCustomMemoryCacheSize() {\n    harness.memoryCacheScreens = 9.5f;\n    Shadows.shadowOf(harness.activityManager).setMemoryClass(getLargeEnoughMemoryClass());\n\n    float memoryCacheSize = harness.getCalculator().getMemoryCacheSize();\n\n    assertThat(memoryCacheSize).isEqualTo(harness.getScreenSize() * harness.memoryCacheScreens);\n  }\n\n  @Test\n  public void testDefaultMemoryCacheSizeIsLimitedByMemoryClass() {\n    final int memoryClassBytes =\n        Math.round(harness.getScreenSize() * harness.memoryCacheScreens * harness.sizeMultiplier);\n\n    Shadows.shadowOf(harness.activityManager).setMemoryClass(memoryClassBytes / (1024 * 1024));\n\n    float memoryCacheSize = harness.getCalculator().getMemoryCacheSize();\n\n    assertThat(memoryCacheSize).isIn(Range.atMost(memoryClassBytes * harness.sizeMultiplier));\n  }\n\n  @Test\n  public void testDefaultBitmapPoolSize() {\n    Shadows.shadowOf(harness.activityManager).setMemoryClass(getLargeEnoughMemoryClass());\n\n    float bitmapPoolSize = harness.getCalculator().getBitmapPoolSize();\n\n    assertThat(bitmapPoolSize).isEqualTo(harness.getScreenSize() * harness.bitmapPoolScreens);\n  }\n\n  @Test\n  public void testCanSetCustomBitmapPoolSize() {\n    harness.bitmapPoolScreens = 2f;\n    Shadows.shadowOf(harness.activityManager).setMemoryClass(getLargeEnoughMemoryClass());\n\n    float bitmapPoolSize = harness.getCalculator().getBitmapPoolSize();\n\n    assertThat(bitmapPoolSize).isEqualTo(harness.getScreenSize() * harness.bitmapPoolScreens);\n  }\n\n  @Test\n  public void testDefaultBitmapPoolSizeIsLimitedByMemoryClass() {\n    final int memoryClassBytes =\n        Math.round(harness.getScreenSize() * harness.bitmapPoolScreens * harness.sizeMultiplier);\n\n    Shadows.shadowOf(harness.activityManager).setMemoryClass(memoryClassBytes / (1024 * 1024));\n\n    int bitmapPoolSize = harness.getCalculator().getBitmapPoolSize();\n\n    assertThat((float) bitmapPoolSize)\n        .isIn(Range.atMost(memoryClassBytes * harness.sizeMultiplier));\n  }\n\n  @Test\n  public void testCumulativePoolAndMemoryCacheSizeAreLimitedByMemoryClass() {\n    final int memoryClassBytes =\n        Math.round(\n            harness.getScreenSize()\n                * (harness.bitmapPoolScreens + harness.memoryCacheScreens)\n                * harness.sizeMultiplier);\n    Shadows.shadowOf(harness.activityManager).setMemoryClass(memoryClassBytes / (1024 * 1024));\n\n    int memoryCacheSize = harness.getCalculator().getMemoryCacheSize();\n    int bitmapPoolSize = harness.getCalculator().getBitmapPoolSize();\n\n    assertThat((float) memoryCacheSize + bitmapPoolSize)\n        .isIn(Range.atMost(memoryClassBytes * harness.sizeMultiplier));\n  }\n\n  @Test\n  public void testCumulativePoolAndMemoryCacheSizesAreSmallerOnLowMemoryDevices() {\n    Shadows.shadowOf(harness.activityManager).setMemoryClass(getLargeEnoughMemoryClass() / 2);\n    final int normalMemoryCacheSize = harness.getCalculator().getMemoryCacheSize();\n    final int normalBitmapPoolSize = harness.getCalculator().getBitmapPoolSize();\n\n    Util.setSdkVersionInt(10);\n\n    // Keep the bitmap pool size constant, even though normally it would change.\n    harness.byteArrayPoolSizeBytes *= 2;\n    final int smallMemoryCacheSize = harness.getCalculator().getMemoryCacheSize();\n    final int smallBitmapPoolSize = harness.getCalculator().getBitmapPoolSize();\n\n    assertThat(smallMemoryCacheSize).isLessThan(normalMemoryCacheSize);\n    assertThat(smallBitmapPoolSize).isLessThan(normalBitmapPoolSize);\n  }\n\n  @Test\n  public void testByteArrayPoolSize_withLowRamDevice_isHalfTheSpecifiedBytes() {\n    LowRamActivityManager activityManager = Shadow.extract(harness.activityManager);\n    activityManager.setMemoryClass(getLargeEnoughMemoryClass());\n    activityManager.setIsLowRam();\n\n    int byteArrayPoolSize = harness.getCalculator().getArrayPoolSizeInBytes();\n    assertThat(byteArrayPoolSize).isEqualTo(harness.byteArrayPoolSizeBytes / 2);\n  }\n\n  private int getLargeEnoughMemoryClass() {\n    float totalScreenBytes =\n        harness.getScreenSize() * (harness.bitmapPoolScreens + harness.memoryCacheScreens);\n    float totalBytes = totalScreenBytes + harness.byteArrayPoolSizeBytes;\n    // Memory class is in mb, not bytes!\n    float totalMb = totalBytes / (1024 * 1024);\n    float memoryClassMb = totalMb / harness.sizeMultiplier;\n    return (int) Math.ceil(memoryClassMb);\n  }\n\n  private static class MemorySizeHarness {\n    final int pixelSize = 500;\n    final int bytesPerPixel = MemorySizeCalculator.BYTES_PER_ARGB_8888_PIXEL;\n    float memoryCacheScreens = MemorySizeCalculator.Builder.MEMORY_CACHE_TARGET_SCREENS;\n    float bitmapPoolScreens = MemorySizeCalculator.Builder.BITMAP_POOL_TARGET_SCREENS;\n    final float sizeMultiplier = MemorySizeCalculator.Builder.MAX_SIZE_MULTIPLIER;\n    int byteArrayPoolSizeBytes = MemorySizeCalculator.Builder.ARRAY_POOL_SIZE_BYTES;\n    final ActivityManager activityManager =\n        (ActivityManager)\n            ApplicationProvider.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);\n    final MemorySizeCalculator.ScreenDimensions screenDimensions =\n        mock(MemorySizeCalculator.ScreenDimensions.class);\n\n    MemorySizeCalculator getCalculator() {\n      when(screenDimensions.getWidthPixels()).thenReturn(pixelSize);\n      when(screenDimensions.getHeightPixels()).thenReturn(pixelSize);\n      return new MemorySizeCalculator.Builder(ApplicationProvider.getApplicationContext())\n          .setMemoryCacheScreens(memoryCacheScreens)\n          .setBitmapPoolScreens(bitmapPoolScreens)\n          .setMaxSizeMultiplier(sizeMultiplier)\n          .setActivityManager(activityManager)\n          .setScreenDimensions(screenDimensions)\n          .setArrayPoolSize(byteArrayPoolSizeBytes)\n          .build();\n    }\n\n    int getScreenSize() {\n      return pixelSize * pixelSize * bytesPerPixel;\n    }\n  }\n\n  @Implements(ActivityManager.class)\n  public static final class LowRamActivityManager extends ShadowActivityManager {\n\n    private boolean isLowRam;\n\n    void setIsLowRam() {\n      this.isLowRam = true;\n    }\n\n    @Implementation\n    @Override\n    public boolean isLowRamDevice() {\n      return isLowRam;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/cache/SafeKeyGeneratorTest.java",
    "content": "package com.bumptech.glide.load.engine.cache;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertTrue;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport java.security.MessageDigest;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class SafeKeyGeneratorTest {\n  private SafeKeyGenerator keyGenerator;\n  private int nextId;\n\n  @Before\n  public void setUp() {\n    nextId = 0;\n    keyGenerator = new SafeKeyGenerator();\n  }\n\n  @Test\n  public void testKeysAreValidForDiskCache() {\n    final Pattern diskCacheRegex = Pattern.compile(\"[a-z0-9_-]{64}\");\n    for (int i = 0; i < 1000; i++) {\n      String key = getRandomKeyFromGenerator();\n      Matcher matcher = diskCacheRegex.matcher(key);\n      assertTrue(key, matcher.matches());\n    }\n  }\n\n  private String getRandomKeyFromGenerator() {\n    return keyGenerator.getSafeKey(new MockKey(getNextId()));\n  }\n\n  private String getNextId() {\n    return String.valueOf(nextId++);\n  }\n\n  private static final class MockKey implements Key {\n    private final String id;\n\n    MockKey(String id) {\n      this.id = id;\n    }\n\n    @Override\n    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {\n      messageDigest.update(id.getBytes(CHARSET));\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/executor/GlideExecutorTest.java",
    "content": "package com.bumptech.glide.load.engine.executor;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\n\nimport androidx.annotation.NonNull;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class GlideExecutorTest {\n\n  @Test\n  public void testOnExecuteDecorator_isCalledAndCanDecorateRunnable() throws InterruptedException {\n    final CountDownLatch decoratorCalled = new CountDownLatch(1);\n    final CountDownLatch decoratedRunnableExecuted = new CountDownLatch(1);\n\n    GlideExecutor executor =\n        GlideExecutor.newDiskCacheBuilder()\n            .experimentalSetOnExecuteDecorator(\n                new Function<Runnable, Runnable>() {\n                  @Override\n                  public Runnable apply(Runnable runnable) {\n                    decoratorCalled.countDown();\n                    return new Runnable() {\n                      @Override\n                      public void run() {\n                        decoratedRunnableExecuted.countDown();\n                        runnable.run();\n                      }\n                    };\n                  }\n                })\n            .build();\n\n    final CountDownLatch originalRunnableExecuted = new CountDownLatch(1);\n    executor.execute(\n        new Runnable() {\n          @Override\n          public void run() {\n            originalRunnableExecuted.countDown();\n          }\n        });\n\n    assertThat(decoratorCalled.await(100, TimeUnit.MILLISECONDS)).isTrue();\n    assertThat(decoratedRunnableExecuted.await(100, TimeUnit.MILLISECONDS)).isTrue();\n    assertThat(originalRunnableExecuted.await(100, TimeUnit.MILLISECONDS)).isTrue();\n\n    executor.shutdown();\n    executor.awaitTermination(500, TimeUnit.MILLISECONDS);\n  }\n\n  @Test\n  public void testOnExecuteDecorator_notDecorated_decoratorNotCalled() throws InterruptedException {\n    final CountDownLatch decoratorCalled = new CountDownLatch(1);\n    final CountDownLatch decoratedRunnableExecuted = new CountDownLatch(1);\n\n    GlideExecutor executor = GlideExecutor.newDiskCacheBuilder().build();\n\n    final CountDownLatch originalRunnableExecuted = new CountDownLatch(1);\n    executor.execute(\n        new Runnable() {\n          @Override\n          public void run() {\n            originalRunnableExecuted.countDown();\n          }\n        });\n\n    assertThat(decoratorCalled.await(100, TimeUnit.MILLISECONDS)).isFalse();\n    assertThat(decoratedRunnableExecuted.await(100, TimeUnit.MILLISECONDS)).isFalse();\n    assertThat(originalRunnableExecuted.await(100, TimeUnit.MILLISECONDS)).isTrue();\n\n    executor.shutdown();\n    executor.awaitTermination(500, TimeUnit.MILLISECONDS);\n  }\n\n  @Test\n  public void testLoadsAreExecutedInOrder() throws InterruptedException {\n    final List<Integer> resultPriorities = Collections.synchronizedList(new ArrayList<Integer>());\n    CountDownLatch latch = new CountDownLatch(1);\n    GlideExecutor executor = GlideExecutor.newDiskCacheExecutor();\n    for (int i = 5; i > 0; i--) {\n      executor.execute(\n          new MockRunnable(\n              i,\n              new MockRunnable.OnRun() {\n                @Override\n                public void onRun(int priority) {\n                  try {\n                    latch.await();\n                  } catch (InterruptedException e) {\n                    Thread.currentThread().interrupt();\n                    throw new RuntimeException(e);\n                  }\n                  resultPriorities.add(priority);\n                }\n              }));\n    }\n    latch.countDown();\n\n    executor.shutdown();\n    executor.awaitTermination(500, TimeUnit.MILLISECONDS);\n\n    // Since no jobs are queued, the first item added will be run immediately, regardless of\n    // priority.\n    assertThat(resultPriorities).containsExactly(5, 1, 2, 3, 4).inOrder();\n  }\n\n  private static final class MockRunnable implements Runnable, Comparable<MockRunnable> {\n    private final int priority;\n    private final OnRun onRun;\n\n    @Override\n    public int compareTo(@NonNull MockRunnable another) {\n      return priority - another.priority;\n    }\n\n    interface OnRun {\n      void onRun(int priority);\n    }\n\n    MockRunnable(int priority, OnRun onRun) {\n      this.priority = priority;\n      this.onRun = onRun;\n    }\n\n    @Override\n    public void run() {\n      onRun.onRun(priority);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunnerTest.java",
    "content": "package com.bumptech.glide.load.engine.prefill;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.anyResource;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertNotEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.atLeastOnce;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport android.os.Handler;\nimport android.util.Log;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.load.engine.cache.MemoryCacheAdapter;\nimport com.bumptech.glide.load.resource.bitmap.BitmapResource;\nimport com.bumptech.glide.tests.Util.CreateBitmap;\nimport com.bumptech.glide.util.Util;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.shadows.ShadowLog;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapPreFillRunnerTest {\n  @Mock private BitmapPreFillRunner.Clock clock;\n  @Mock private BitmapPool pool;\n  @Mock private MemoryCache cache;\n  @Mock private Handler mainHandler;\n  private final List<Bitmap> addedBitmaps = new ArrayList<>();\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    doAnswer(new AddBitmapPoolAnswer(addedBitmaps)).when(pool).put(any(Bitmap.class));\n    when(pool.getDirty(anyInt(), anyInt(), any(Bitmap.Config.class)))\n        .thenAnswer(new CreateBitmap());\n    when(cache.put(any(Key.class), anyResource()))\n        .thenAnswer(new AddBitmapCacheAnswer(addedBitmaps));\n  }\n\n  private BitmapPreFillRunner getHandler(Map<PreFillType, Integer> allocationOrder) {\n    return new BitmapPreFillRunner(\n        pool, cache, new PreFillQueue(allocationOrder), clock, mainHandler);\n  }\n\n  @Test\n  public void testAllocatesABitmapPerSizeInAllocationOrder() {\n    PreFillType size = new PreFillType.Builder(100).setConfig(Bitmap.Config.ARGB_8888).build();\n    final int toAdd = 3;\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, toAdd);\n    BitmapPreFillRunner handler = getHandler(allocationOrder);\n    handler.run();\n\n    Bitmap expected = Bitmap.createBitmap(size.getWidth(), size.getHeight(), size.getConfig());\n    // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed\n    // assertThat(addedBitmaps).containsExactly(expected, expected, expected);\n  }\n\n  @Test\n  public void testAllocatesBitmapsInOrderGivenByAllocationOrder() {\n    PreFillType smallWidth =\n        new PreFillType.Builder(50, 100).setConfig(Bitmap.Config.ARGB_8888).build();\n    PreFillType smallHeight =\n        new PreFillType.Builder(100, 50).setConfig(Bitmap.Config.RGB_565).build();\n\n    PreFillType[] expectedOrder =\n        new PreFillType[] {\n          smallWidth, smallHeight, smallWidth, smallHeight,\n        };\n\n    HashMap<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(smallWidth, 2);\n    allocationOrder.put(smallHeight, 2);\n    BitmapPreFillRunner handler = getHandler(allocationOrder);\n    handler.run();\n\n    Bitmap[] expectedBitmaps = new Bitmap[expectedOrder.length];\n    for (int i = 0; i < expectedBitmaps.length; i++) {\n      PreFillType current = expectedOrder[i];\n      expectedBitmaps[i] =\n          Bitmap.createBitmap(current.getWidth(), current.getHeight(), current.getConfig());\n    }\n\n    Bitmap current = addedBitmaps.get(0);\n    for (int i = 1; i < addedBitmaps.size(); i++) {\n      assertNotEquals(current, addedBitmaps.get(i));\n      current = addedBitmaps.get(i);\n    }\n\n    assertThat(addedBitmaps).hasSize(4);\n  }\n\n  @Test\n  public void testStopsAllocatingBitmapsUntilNextIdleCallIfAllocationsTakeLongerThanLimit() {\n    PreFillType size = new PreFillType.Builder(1).setConfig(Bitmap.Config.ARGB_8888).build();\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, 3);\n    when(clock.now()).thenReturn(0L).thenReturn(0L).thenReturn(BitmapPreFillRunner.MAX_DURATION_MS);\n    BitmapPreFillRunner handler = getHandler(allocationOrder);\n    handler.run();\n\n    assertThat(addedBitmaps).hasSize(1);\n\n    handler.run();\n\n    assertThat(addedBitmaps).hasSize(3);\n  }\n\n  @Test\n  public void testPreFillHandlerDoesNotPostIfHasNoBitmapsToAllocate() {\n    BitmapPreFillRunner handler = getHandler(new HashMap<PreFillType, Integer>());\n    handler.run();\n    verify(mainHandler, never()).postDelayed(any(Runnable.class), anyLong());\n  }\n\n  @Test\n  public void testPreFillHandlerPostsIfHasBitmapsToAllocateAfterRunning() {\n    PreFillType size = new PreFillType.Builder(1).setConfig(Bitmap.Config.ARGB_8888).build();\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, 2);\n    BitmapPreFillRunner handler = getHandler(allocationOrder);\n    when(clock.now()).thenReturn(0L).thenReturn(0L).thenReturn(BitmapPreFillRunner.MAX_DURATION_MS);\n\n    handler.run();\n    verify(mainHandler).postDelayed(eq(handler), anyLong());\n  }\n\n  @Test\n  public void testPreFillHandlerPostsWithBackoffIfHasBitmapsToAllocateAfterRunning() {\n    PreFillType size = new PreFillType.Builder(1).setConfig(Bitmap.Config.ARGB_8888).build();\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, 100);\n\n    BitmapPreFillRunner handler = getHandler(allocationOrder);\n    when(clock.now()).thenReturn(0L).thenReturn(0L).thenReturn(BitmapPreFillRunner.MAX_DURATION_MS);\n\n    handler.run();\n    verify(mainHandler).postDelayed(eq(handler), eq(BitmapPreFillRunner.INITIAL_BACKOFF_MS));\n\n    when(clock.now())\n        .thenReturn(BitmapPreFillRunner.MAX_DURATION_MS)\n        .thenReturn(\n            BitmapPreFillRunner.MAX_DURATION_MS\n                + BitmapPreFillRunner.INITIAL_BACKOFF_MS * BitmapPreFillRunner.BACKOFF_RATIO);\n\n    handler.run();\n\n    verify(mainHandler)\n        .postDelayed(\n            eq(handler),\n            eq(BitmapPreFillRunner.INITIAL_BACKOFF_MS * BitmapPreFillRunner.BACKOFF_RATIO));\n\n    when(clock.now()).thenReturn(0L).thenReturn(BitmapPreFillRunner.MAX_DURATION_MS);\n    handler.run();\n    when(clock.now()).thenReturn(0L).thenReturn(BitmapPreFillRunner.MAX_DURATION_MS);\n    handler.run();\n    when(clock.now()).thenReturn(0L).thenReturn(BitmapPreFillRunner.MAX_DURATION_MS);\n    handler.run();\n    when(clock.now()).thenReturn(0L).thenReturn(BitmapPreFillRunner.MAX_DURATION_MS);\n    handler.run();\n\n    verify(mainHandler, atLeastOnce())\n        .postDelayed(eq(handler), eq(BitmapPreFillRunner.MAX_BACKOFF_MS));\n  }\n\n  @Test\n  public void testPreFillHandlerDoesNotPostIfHasBitmapsButIsCancelled() {\n    PreFillType size = new PreFillType.Builder(1).setConfig(Bitmap.Config.ARGB_8888).build();\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, 2);\n\n    BitmapPreFillRunner handler = getHandler(allocationOrder);\n    when(clock.now()).thenReturn(0L).thenReturn(0L).thenReturn(BitmapPreFillRunner.MAX_DURATION_MS);\n    handler.cancel();\n    handler.run();\n\n    verify(mainHandler, never()).postDelayed(any(Runnable.class), anyLong());\n  }\n\n  @Test\n  public void testAddsBitmapsToMemoryCacheIfMemoryCacheHasEnoughSpaceRemaining() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(cache.getMaxSize()).thenReturn(Long.valueOf(Util.getBitmapByteSize(bitmap)));\n\n    PreFillType size =\n        new PreFillType.Builder(bitmap.getWidth(), bitmap.getHeight())\n            .setConfig(bitmap.getConfig())\n            .build();\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, 1);\n\n    getHandler(allocationOrder).run();\n\n    verify(cache).put(any(Key.class), anyResource());\n    verify(pool, never()).put(any(Bitmap.class));\n    // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed\n    // assertThat(addedBitmaps).containsExactly(bitmap);\n  }\n\n  @Test\n  public void testAddsBitmapsToBitmapPoolIfMemoryCacheIsFull() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(cache.getMaxSize()).thenReturn(0L);\n\n    PreFillType size =\n        new PreFillType.Builder(bitmap.getWidth(), bitmap.getHeight())\n            .setConfig(bitmap.getConfig())\n            .build();\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, 1);\n\n    getHandler(allocationOrder).run();\n\n    verify(cache, never()).put(any(Key.class), anyResource());\n    // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed\n    // verify(pool).put(eq(bitmap));\n    // assertThat(addedBitmaps).containsExactly(bitmap);\n  }\n\n  @Test\n  public void testAddsBitmapsToPoolIfMemoryCacheIsNotFullButCannotFitBitmap() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(cache.getMaxSize()).thenReturn((long) Util.getBitmapByteSize(bitmap) / 2);\n\n    PreFillType size =\n        new PreFillType.Builder(bitmap.getWidth(), bitmap.getHeight())\n            .setConfig(bitmap.getConfig())\n            .build();\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, 1);\n\n    getHandler(allocationOrder).run();\n\n    verify(cache, never()).put(any(Key.class), anyResource());\n    // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed\n    // verify(pool).put(eq(bitmap));\n    // assertThat(addedBitmaps).containsExactly(bitmap);\n  }\n\n  @Test\n  public void testDoesAGetFromPoolBeforeAddingForEachSize() {\n    Bitmap first = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_4444);\n    PreFillType firstSize =\n        new PreFillType.Builder(first.getWidth(), first.getHeight())\n            .setConfig(first.getConfig())\n            .build();\n\n    Bitmap second = Bitmap.createBitmap(200, 200, Bitmap.Config.RGB_565);\n    PreFillType secondSize =\n        new PreFillType.Builder(second.getWidth(), second.getHeight())\n            .setConfig(second.getConfig())\n            .build();\n\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(firstSize, 1);\n    allocationOrder.put(secondSize, 1);\n\n    getHandler(allocationOrder).run();\n\n    InOrder firstOrder = inOrder(pool);\n    firstOrder\n        .verify(pool)\n        .getDirty(eq(first.getWidth()), eq(first.getHeight()), eq(first.getConfig()));\n    // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed\n    // firstOrder.verify(pool).put(eq(first));\n\n    InOrder secondOrder = inOrder(pool);\n    secondOrder\n        .verify(pool)\n        .getDirty(eq(second.getWidth()), eq(second.getHeight()), eq(second.getConfig()));\n    // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed\n    // secondOrder.verify(pool).put(eq(second));\n  }\n\n  @Test\n  public void testDoesNotGetMoreThanOncePerSize() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_4444);\n    PreFillType size =\n        new PreFillType.Builder(bitmap.getWidth(), bitmap.getHeight())\n            .setConfig(bitmap.getConfig())\n            .build();\n\n    final int numBitmaps = 5;\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(size, numBitmaps);\n\n    getHandler(allocationOrder).run();\n\n    InOrder order = inOrder(pool);\n    order\n        .verify(pool)\n        .getDirty(eq(bitmap.getWidth()), eq(bitmap.getHeight()), eq(bitmap.getConfig()));\n    // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed\n    // order.verify(pool, times(numBitmaps)).put(eq(bitmap));\n  }\n\n  @Test\n  public void allocate_whenBitmapPoolIsAtCapacity_doesNotLogWithRecycledBitmap() {\n    ShadowLog.setLoggable(BitmapPreFillRunner.TAG, Log.VERBOSE);\n\n    int dimensions = 10;\n    Bitmap.Config config = Bitmap.Config.ARGB_8888;\n    int bitmapByteSize = Util.getBitmapByteSize(dimensions, dimensions, config);\n    PreFillType preFillType = new PreFillType.Builder(dimensions).setConfig(config).build();\n    Map<PreFillType, Integer> allocationOrder = new HashMap<>();\n    allocationOrder.put(preFillType, 1);\n    PreFillQueue queue = new PreFillQueue(allocationOrder);\n    BitmapPreFillRunner runner =\n        new BitmapPreFillRunner(\n            new LruBitmapPool(bitmapByteSize - 1), new MemoryCacheAdapter(), queue);\n\n    runner.allocate();\n  }\n\n  private static final class AddBitmapPoolAnswer implements Answer<Void> {\n    private final List<Bitmap> bitmaps;\n\n    AddBitmapPoolAnswer(List<Bitmap> bitmaps) {\n      this.bitmaps = bitmaps;\n    }\n\n    @Override\n    public Void answer(InvocationOnMock invocationOnMock) throws Throwable {\n      Bitmap bitmap = (Bitmap) invocationOnMock.getArguments()[0];\n      bitmaps.add(bitmap);\n      return null;\n    }\n  }\n\n  private static final class AddBitmapCacheAnswer implements Answer<Resource<?>> {\n    private final List<Bitmap> bitmaps;\n\n    AddBitmapCacheAnswer(List<Bitmap> bitmaps) {\n      this.bitmaps = bitmaps;\n    }\n\n    @Override\n    public Resource<?> answer(InvocationOnMock invocationOnMock) throws Throwable {\n      BitmapResource resource = (BitmapResource) invocationOnMock.getArguments()[1];\n      bitmaps.add(resource.get());\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillerTest.java",
    "content": "package com.bumptech.glide.load.engine.prefill;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.fail;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.cache.MemoryCache;\nimport com.bumptech.glide.tests.Util.CreateBitmap;\nimport com.bumptech.glide.util.Util;\nimport com.google.common.collect.Range;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapPreFillerTest {\n  private static final int DEFAULT_BITMAP_WIDTH = 100;\n  private static final int DEFAULT_BITMAP_HEIGHT = 50;\n\n  private static final int BITMAPS_IN_POOL = 10;\n  private static final int BITMAPS_IN_CACHE = 10;\n\n  private final Bitmap.Config defaultBitmapConfig = PreFillType.DEFAULT_CONFIG;\n  private final Bitmap defaultBitmap =\n      Bitmap.createBitmap(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT, defaultBitmapConfig);\n  private final long defaultBitmapSize = Util.getBitmapByteSize(defaultBitmap);\n  private final long poolSize = BITMAPS_IN_CACHE * defaultBitmapSize;\n  private final long cacheSize = BITMAPS_IN_POOL * defaultBitmapSize;\n\n  @Mock private BitmapPool pool;\n  @Mock private MemoryCache cache;\n  private BitmapPreFiller bitmapPreFiller;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    when(pool.getMaxSize()).thenReturn(poolSize);\n    when(pool.getDirty(anyInt(), anyInt(), any(Bitmap.Config.class)))\n        .thenAnswer(new CreateBitmap());\n    when(cache.getMaxSize()).thenReturn(cacheSize);\n\n    bitmapPreFiller = new BitmapPreFiller(cache, pool, DecodeFormat.DEFAULT);\n  }\n\n  @Test\n  public void testAllocationOrderContainsEnoughSizesToFillPoolAndMemoryCache() {\n    PreFillQueue allocationOrder =\n        bitmapPreFiller.generateAllocationOrder(\n            new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT)\n                .setConfig(defaultBitmapConfig)\n                .build());\n\n    assertEquals(BITMAPS_IN_POOL + BITMAPS_IN_CACHE, allocationOrder.getSize());\n  }\n\n  @Test\n  public void testAllocationOrderThatDoesNotFitExactlyIntoGivenSizeRoundsDown() {\n    PreFillType[] sizes =\n        new PreFillType[] {\n          new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT)\n              .setConfig(defaultBitmapConfig)\n              .build(),\n          new PreFillType.Builder(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT)\n              .setConfig(defaultBitmapConfig)\n              .build(),\n          new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 2)\n              .setConfig(defaultBitmapConfig)\n              .build(),\n        };\n    PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(sizes);\n\n    int byteSize = 0;\n    while (!allocationOrder.isEmpty()) {\n      PreFillType current = allocationOrder.remove();\n      byteSize +=\n          Util.getBitmapByteSize(current.getWidth(), current.getHeight(), current.getConfig());\n    }\n\n    int expectedSize = 0;\n    long maxSize = poolSize + cacheSize;\n    for (PreFillType current : sizes) {\n      int currentSize =\n          Util.getBitmapByteSize(current.getWidth(), current.getHeight(), current.getConfig());\n      // See https://errorprone.info/bugpattern/NarrowingCompoundAssignment.\n      expectedSize = (int) (expectedSize + (currentSize * (maxSize / (3 * currentSize))));\n    }\n\n    assertEquals(expectedSize, byteSize);\n  }\n\n  @Test\n  public void testAllocationOrderDoesNotOverFillWithMultipleSizes() {\n    PreFillQueue allocationOrder =\n        bitmapPreFiller.generateAllocationOrder(\n            new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT)\n                .setConfig(defaultBitmapConfig)\n                .build(),\n            new PreFillType.Builder(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT)\n                .setConfig(defaultBitmapConfig)\n                .build(),\n            new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 2)\n                .setConfig(defaultBitmapConfig)\n                .build());\n\n    long byteSize = 0;\n    while (!allocationOrder.isEmpty()) {\n      PreFillType current = allocationOrder.remove();\n      byteSize +=\n          Util.getBitmapByteSize(current.getWidth(), current.getHeight(), current.getConfig());\n    }\n\n    assertThat(byteSize).isIn(Range.atMost(poolSize + cacheSize));\n  }\n\n  @Test\n  public void testAllocationOrderDoesNotOverFillWithMultipleSizesAndWeights() {\n    PreFillQueue allocationOrder =\n        bitmapPreFiller.generateAllocationOrder(\n            new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT)\n                .setConfig(defaultBitmapConfig)\n                .setWeight(4)\n                .build(),\n            new PreFillType.Builder(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT)\n                .setConfig(defaultBitmapConfig)\n                .build(),\n            new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 3)\n                .setConfig(defaultBitmapConfig)\n                .setWeight(3)\n                .build());\n\n    long byteSize = 0;\n    while (!allocationOrder.isEmpty()) {\n      PreFillType current = allocationOrder.remove();\n      byteSize +=\n          Util.getBitmapByteSize(current.getWidth(), current.getHeight(), current.getConfig());\n    }\n\n    assertThat(byteSize).isIn(Range.atMost(poolSize + cacheSize));\n  }\n\n  @Test\n  public void testAllocationOrderContainsSingleSizeIfSingleSizeIsProvided() {\n    PreFillQueue allocationOrder =\n        bitmapPreFiller.generateAllocationOrder(\n            new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT)\n                .setConfig(defaultBitmapConfig)\n                .build());\n\n    while (!allocationOrder.isEmpty()) {\n      PreFillType size = allocationOrder.remove();\n      assertEquals(DEFAULT_BITMAP_WIDTH, size.getWidth());\n      assertEquals(DEFAULT_BITMAP_HEIGHT, size.getHeight());\n      assertEquals(defaultBitmapConfig, size.getConfig());\n    }\n  }\n\n  @Test\n  public void testAllocationOrderSplitsEvenlyBetweenEqualSizesWithEqualWeights() {\n    PreFillType smallWidth =\n        new PreFillType.Builder(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT)\n            .setConfig(defaultBitmapConfig)\n            .build();\n    PreFillType smallHeight =\n        new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 2)\n            .setConfig(defaultBitmapConfig)\n            .build();\n    PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(smallWidth, smallHeight);\n\n    int numSmallWidth = 0;\n    int numSmallHeight = 0;\n    while (!allocationOrder.isEmpty()) {\n      PreFillType current = allocationOrder.remove();\n      if (smallWidth.equals(current)) {\n        numSmallWidth++;\n      } else if (smallHeight.equals(current)) {\n        numSmallHeight++;\n      } else {\n        fail(\"Unexpected size, size: \" + current);\n      }\n    }\n\n    assertEquals(numSmallWidth, numSmallHeight);\n  }\n\n  @Test\n  public void testAllocationOrderSplitsByteSizeEvenlyBetweenUnEqualSizesWithEqualWeights() {\n    PreFillType smallWidth =\n        new PreFillType.Builder(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT)\n            .setConfig(defaultBitmapConfig)\n            .build();\n    PreFillType normal =\n        new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT)\n            .setConfig(defaultBitmapConfig)\n            .build();\n    PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(smallWidth, normal);\n\n    int numSmallWidth = 0;\n    int numNormal = 0;\n    while (!allocationOrder.isEmpty()) {\n      PreFillType current = allocationOrder.remove();\n      if (smallWidth.equals(current)) {\n        numSmallWidth++;\n      } else if (normal.equals(current)) {\n        numNormal++;\n      } else {\n        fail(\"Unexpected size, size: \" + current);\n      }\n    }\n\n    assertEquals(2 * numNormal, numSmallWidth);\n  }\n\n  @Test\n  public void testAllocationOrderSplitsByteSizeUnevenlyBetweenEqualSizesWithUnequalWeights() {\n    PreFillType doubleWeight =\n        new PreFillType.Builder(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT)\n            .setConfig(defaultBitmapConfig)\n            .setWeight(2)\n            .build();\n    PreFillType normal =\n        new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 2)\n            .setConfig(defaultBitmapConfig)\n            .build();\n    PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(doubleWeight, normal);\n\n    int numDoubleWeight = 0;\n    int numNormal = 0;\n    while (!allocationOrder.isEmpty()) {\n      PreFillType current = allocationOrder.remove();\n      if (doubleWeight.equals(current)) {\n        numDoubleWeight++;\n      } else if (normal.equals(current)) {\n        numNormal++;\n      } else {\n        fail(\"Unexpected size, size: \" + current);\n      }\n    }\n\n    assertEquals(2 * numNormal, numDoubleWeight);\n  }\n\n  @Test\n  public void testAllocationOrderRoundRobinsDifferentSizes() {\n    when(pool.getMaxSize()).thenReturn(defaultBitmapSize);\n    when(cache.getMaxSize()).thenReturn(defaultBitmapSize);\n    PreFillType smallWidth =\n        new PreFillType.Builder(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT)\n            .setConfig(defaultBitmapConfig)\n            .build();\n    PreFillType smallHeight =\n        new PreFillType.Builder(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 2)\n            .setConfig(defaultBitmapConfig)\n            .build();\n\n    PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(smallWidth, smallHeight);\n\n    List<PreFillType> attributes = new ArrayList<>();\n    while (!allocationOrder.isEmpty()) {\n      attributes.add(allocationOrder.remove());\n    }\n\n    // Either width, height, width, height or height, width, height, width.\n    try {\n      assertThat(attributes)\n          .containsExactly(smallWidth, smallHeight, smallWidth, smallHeight)\n          .inOrder();\n    } catch (AssertionError e) {\n      assertThat(attributes)\n          .containsExactly(smallHeight, smallWidth, smallHeight, smallWidth)\n          .inOrder();\n    }\n  }\n\n  @Test\n  @SuppressWarnings(\"deprecation\")\n  public void testSetsConfigOnBuildersToDefaultIfNotSet() {\n    PreFillType.Builder builder = mock(PreFillType.Builder.class);\n    when(builder.build())\n        .thenReturn(new PreFillType.Builder(100).setConfig(Bitmap.Config.RGB_565).build());\n\n    bitmapPreFiller.preFill(builder);\n\n    InOrder order = inOrder(builder);\n    order\n        .verify(builder)\n        .setConfig(\n            DecodeFormat.DEFAULT == DecodeFormat.PREFER_ARGB_8888\n                ? Bitmap.Config.ARGB_8888\n                : Bitmap.Config.RGB_565);\n    order.verify(builder).build();\n  }\n\n  @Test\n  public void testDoesNotSetConfigOnBuildersIfConfigIsAlreadySet() {\n    PreFillType.Builder builder = mock(PreFillType.Builder.class);\n\n    when(builder.getConfig()).thenReturn(Bitmap.Config.ARGB_4444);\n    when(builder.build())\n        .thenReturn(new PreFillType.Builder(100).setConfig(Bitmap.Config.ARGB_4444).build());\n    bitmapPreFiller.preFill(builder);\n\n    verify(builder, never()).setConfig(any(Bitmap.Config.class));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/engine/prefill/PreFillTypeTest.java",
    "content": "package com.bumptech.glide.load.engine.prefill;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\n\nimport android.graphics.Bitmap;\nimport com.google.common.testing.EqualsTester;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class PreFillTypeTest {\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfSizeIsZero() {\n    new PreFillType.Builder(0);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfWidthIsZero() {\n    new PreFillType.Builder(0, 100);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfHeightIsZero() {\n    new PreFillType.Builder(100, 0);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfWeightIsZero() {\n    new PreFillType.Builder(100).setWeight(0);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testConstructorThrowsIfConfigIsNull() {\n    new PreFillType(100, 100, null, 1);\n  }\n\n  @Test\n  public void testGetWidthReturnsGivenWidth() {\n    int width = 500;\n    assertEquals(width, new PreFillType(width, 100, Bitmap.Config.ARGB_4444, 1).getWidth());\n  }\n\n  @Test\n  public void testGetHeightReturnsGivenHeight() {\n    int height = 123;\n    assertEquals(height, new PreFillType(100, height, Bitmap.Config.ARGB_4444, 1).getHeight());\n  }\n\n  @Test\n  public void testGetConfigReturnsGivenConfig() {\n    Bitmap.Config config = Bitmap.Config.ARGB_8888;\n    assertEquals(config, new PreFillType(100, 100, config, 1).getConfig());\n  }\n\n  @Test\n  public void testGetWeightReturnsGivenWeight() {\n    int weight = 400;\n    assertEquals(weight, new PreFillType(100, 100, Bitmap.Config.ARGB_4444, weight).getWeight());\n  }\n\n  @Test\n  public void testEquality() {\n    new EqualsTester()\n        .addEqualityGroup(\n            new PreFillType(100, 100, Bitmap.Config.ARGB_4444, 1),\n            new PreFillType(100, 100, Bitmap.Config.ARGB_4444, 1))\n        .addEqualityGroup(new PreFillType(200, 100, Bitmap.Config.ARGB_4444, 1))\n        .addEqualityGroup(new PreFillType(100, 200, Bitmap.Config.ARGB_4444, 1))\n        .addEqualityGroup(new PreFillType(100, 100, Bitmap.Config.ARGB_8888, 1))\n        .addEqualityGroup(new PreFillType(100, 100, Bitmap.Config.ARGB_4444, 2))\n        .testEquals();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/AssetUriLoaderTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\nimport android.content.res.AssetManager;\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.util.Preconditions;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class AssetUriLoaderTest {\n  private static final int IMAGE_SIDE = 10;\n\n  @Mock private AssetUriLoader.AssetFetcherFactory<Object> factory;\n  @Mock private DataFetcher<Object> fetcher;\n  private AssetUriLoader<Object> loader;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    loader = new AssetUriLoader<>(ApplicationProvider.getApplicationContext().getAssets(), factory);\n  }\n\n  @Test\n  public void testHandlesAssetUris() {\n    Uri assetUri = Uri.parse(\"file:///android_asset/assetName\");\n    when(factory.buildFetcher(any(AssetManager.class), eq(\"assetName\"))).thenReturn(fetcher);\n    assertTrue(loader.handles(assetUri));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(\n                loader.buildLoadData(assetUri, IMAGE_SIDE, IMAGE_SIDE, new Options()))\n            .fetcher);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/ByteArrayLoaderTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(JUnit4.class)\npublic class ByteArrayLoaderTest {\n\n  @Mock private ByteArrayLoader.Converter<Object> converter;\n  @Mock private DataFetcher.DataCallback<Object> callback;\n  private ByteArrayLoader<Object> loader;\n  private Options options;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    loader = new ByteArrayLoader<>(converter);\n    options = new Options();\n  }\n\n  @Test\n  public void testCanHandleByteArray() {\n    byte[] data = new byte[10];\n    DataFetcher<Object> fetcher =\n        Preconditions.checkNotNull(loader.buildLoadData(data, -1, -1, options)).fetcher;\n    assertNotNull(fetcher);\n  }\n\n  @Test\n  public void testFetcherReturnsObjectReceivedFromConverter() throws IOException {\n    byte[] data = \"fake\".getBytes(\"UTF-8\");\n    Object expected = new Object();\n    when(converter.convert(eq(data))).thenReturn(expected);\n\n    Preconditions.checkNotNull(loader.buildLoadData(data, 10, 10, options))\n        .fetcher\n        .loadData(Priority.HIGH, callback);\n    verify(callback).onDataReady(eq(expected));\n  }\n\n  @Test\n  public void testFetcherReturnsDataClassFromConverter() {\n    when(converter.getDataClass()).thenReturn(Object.class);\n    assertEquals(\n        Object.class,\n        Preconditions.checkNotNull(loader.buildLoadData(new byte[10], 10, 10, options))\n            .fetcher\n            .getDataClass());\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/DataUrlLoaderTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\nimport android.util.Base64;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n/** Tests for the {@link DataUrlLoader} class. */\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class DataUrlLoaderTest {\n\n  // A valid base64-encoded PNG (a small \"Google\" logo).\n  @SuppressWarnings(\"SpellCheckingInspection\")\n  private static final String VALID_PNG =\n      \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAALCAYAAAAeEY8BAAADFElEQVR42mNgAAK5ig+Cii\"\n          + \"UfSmUL3mVL5r7PE8t5M1U06027eMYLMQZKQUMDE8eyxGrOJYmdDKtCmTHkFfO/iCsUfTykUPFeASH6n1Es+3Wj\"\n          + \"SM5rKQYqANbFcTmsC2OXYpWUKXw/R67ofQEhQ+5FecnfDnYxPJNmzAp35n8Gxv/7pTT+75PQBrFh4iq5b/lk8z\"\n          + \"+aiue+tZDKeaPBMC8qh2leFNgB/xkYGO+Eu+ncCnZRAiuWyHv3VDzngxMui0EWPgpx6n4U4Wx7J8De86aP2blr\"\n          + \"rgaq//fwCv8/KNT//5CU0f99okn/dwse+b9fQECx9IObQvGHMrn8D66See9eiWa9s2GYE57DMCdi6Qs3N+6HIc\"\n          + \"4T70a4mtz2t55909u0jkE85+1Tsdx30ciWSuQ+F+VPe6kskPFc4Z6XRcp9H8t2mNxVF72Gq066K//vZe//v4cD\"\n          + \"ru//ds7V/7dx1MoXf9gtW/zRFGLO+x7x7DeVDDOBDpgZvvSut3nWXR/LyptuxgG33Axzr7rr2TKIZb1eIpL1ej\"\n          + \"co3mGGCWe8cRJMf7FVKO1F/y1Xww4gng6Tu+Ko7X7JTvPo/52Mm//vYMqBO2AbU/H/LUwzpQreT5LOf98PEhPL\"\n          + \"ftslkfvGjGF6aA4QL73halh7y9XgwHVnM2G4b0G+FM549Uw440U7Q+h/eCoVSH0+GYjrrjrr2V530n16w1qdFy\"\n          + \"R+wUYr6YKNRtH/7QzpQHzsfwMDE9gBmxl6/29hcNdu+M8G9HmCWM7bQ6I5bxPBhk0NzmGYErT0mpOe0TVHnY+X\"\n          + \"HXRMQMKrQhkg9omkvZYUSHvZJ5T+Yh3IUoHUZ/mCqc87BdOe2UB9HXzZQWvCeTuNqPO2GgmghAROgFsZ8oCWtg\"\n          + \"BxDNABASC1olmveEQyX/sB8SKRzJcPgbQxw0S/IoaJvksZJvsqXnLQDLhoq7n7nI3GxHOWWs4M1AQ8ic9FhdNf\"\n          + \"7ZRKeyYCjsrUly7AqDzOQC8glP7SFWjhCVhUKiTc5xBIebaAbg4AWcyf+qxNMPXZKoGU57UCqU+KQKGCTwsAbx\"\n          + \"BBmvLaD+cAAAAASUVORK5CYII=\";\n\n  private static final String INVALID_URL_WRONG_SCHEME1 = \"test\";\n  private static final String INVALID_URL_WRONG_SCHEME2 = \"http://google.com\";\n  private static final String INVALID_URL_WRONG_SCHEME3 = \"data:text\";\n  private static final String INVALID_URL_MISSING_COMMA = \"data:image/png;base64=NOT_BASE64\";\n  private static final String INVALID_URL_WRONG_ENCODING = \"data:image/png;base32,\";\n\n  @Mock private MultiModelLoaderFactory multiFactory;\n  private DataUrlLoader<String, InputStream> dataUrlLoader;\n  private DataFetcher<InputStream> fetcher;\n  private Options options;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    DataUrlLoader.StreamFactory<String> factory = new DataUrlLoader.StreamFactory<>();\n    options = new Options();\n    dataUrlLoader = (DataUrlLoader<String, InputStream>) factory.build(multiFactory);\n    fetcher = dataUrlLoader.buildLoadData(VALID_PNG, -1, -1, options).fetcher;\n  }\n\n  @Test\n  public void testHandleDataUri() {\n    assertTrue(dataUrlLoader.handles(VALID_PNG));\n  }\n\n  @Test\n  public void testHandleFalseDataUri() {\n    assertFalse(dataUrlLoader.handles(INVALID_URL_WRONG_SCHEME1));\n    assertFalse(dataUrlLoader.handles(INVALID_URL_WRONG_SCHEME2));\n    assertFalse(dataUrlLoader.handles(INVALID_URL_WRONG_SCHEME3));\n  }\n\n  @Test\n  public void testDecode() throws IOException {\n    byte[] expected =\n        Base64.decode(VALID_PNG.substring(VALID_PNG.indexOf(',') + 1), Base64.DEFAULT);\n    CallBack callback = new CallBack();\n    fetcher.loadData(Priority.HIGH, callback);\n    byte[] result = new byte[((ByteArrayInputStream) callback.data).available()];\n    assertEquals(result.length, ((ByteArrayInputStream) callback.data).read(result));\n    assertTrue(Arrays.equals(result, expected));\n    assertNull(callback.exception);\n  }\n\n  @Test\n  public void testDecodeInvalidScheme() {\n    fetcher = dataUrlLoader.buildLoadData(INVALID_URL_WRONG_SCHEME1, -1, -1, options).fetcher;\n    CallBack callback = new CallBack();\n    fetcher.loadData(Priority.HIGH, callback);\n    assertNotNull(callback.exception);\n  }\n\n  @Test\n  public void testDecodeMissingComma() {\n    fetcher = dataUrlLoader.buildLoadData(INVALID_URL_MISSING_COMMA, -1, -1, options).fetcher;\n    CallBack callback = new CallBack();\n    fetcher.loadData(Priority.HIGH, callback);\n    assertNotNull(callback.exception);\n  }\n\n  @Test\n  public void testDecodeWrongEncoding() {\n    fetcher = dataUrlLoader.buildLoadData(INVALID_URL_WRONG_ENCODING, -1, -1, options).fetcher;\n    CallBack callback = new CallBack();\n    fetcher.loadData(Priority.HIGH, callback);\n    assertNotNull(callback.exception);\n  }\n\n  private static final class CallBack implements DataFetcher.DataCallback<Object> {\n\n    public Object data;\n    public Exception exception;\n\n    @Override\n    public void onDataReady(@Nullable Object data) {\n      this.data = data;\n    }\n\n    @Override\n    public void onLoadFailed(@NonNull Exception e) {\n      this.exception = e;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/GlideUrlTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\n\nimport com.google.common.testing.EqualsTester;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class GlideUrlTest {\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenURLIsNull() {\n    new GlideUrl((URL) null);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfGivenStringUrlIsNull() {\n    new GlideUrl((String) null);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfGivenStringURLIsEmpty() {\n    new GlideUrl(\"\");\n  }\n\n  @Test\n  public void testCanCompareGlideUrlsCreatedWithDifferentTypes() throws MalformedURLException {\n    String stringUrl = \"http://www.google.com\";\n    URL url = new URL(stringUrl);\n\n    assertEquals(new GlideUrl(stringUrl), new GlideUrl(url));\n  }\n\n  @Test\n  public void testCanCompareHashcodeOfGlideUrlsCreatedWithDifferentTypes()\n      throws MalformedURLException {\n    String stringUrl = \"http://nytimes.com\";\n    URL url = new URL(stringUrl);\n\n    assertEquals(new GlideUrl(stringUrl).hashCode(), new GlideUrl(url).hashCode());\n  }\n\n  @Test\n  public void testProducesEquivalentUrlFromString() throws MalformedURLException {\n    String stringUrl = \"http://www.google.com\";\n    GlideUrl glideUrl = new GlideUrl(stringUrl);\n\n    URL url = glideUrl.toURL();\n    assertEquals(stringUrl, url.toString());\n  }\n\n  @Test\n  public void testProducesEquivalentStringFromURL() throws MalformedURLException {\n    String expected = \"http://www.washingtonpost.com\";\n    URL url = new URL(expected);\n    GlideUrl glideUrl = new GlideUrl(url);\n\n    assertEquals(expected, glideUrl.toStringUrl());\n  }\n\n  @Test\n  public void testIssue133() throws MalformedURLException {\n    // u00e0=à\n    final String original =\n        \"http://www.commitstrip.com/wp-content/uploads/2014/07/\"\n            + \"Excel-\\u00E0-toutes-les-sauces-650-finalenglish.jpg\";\n\n    final String escaped =\n        \"http://www.commitstrip.com/wp-content/uploads/2014/07/\"\n            + \"Excel-%C3%A0-toutes-les-sauces-650-finalenglish.jpg\";\n\n    GlideUrl glideUrlFromString = new GlideUrl(original);\n    assertEquals(escaped, glideUrlFromString.toURL().toString());\n\n    GlideUrl glideUrlFromEscapedString = new GlideUrl(escaped);\n    assertEquals(escaped, glideUrlFromEscapedString.toURL().toString());\n\n    GlideUrl glideUrlFromUrl = new GlideUrl(new URL(original));\n    assertEquals(escaped, glideUrlFromUrl.toURL().toString());\n\n    GlideUrl glideUrlFromEscapedUrl = new GlideUrl(new URL(escaped));\n    assertEquals(escaped, glideUrlFromEscapedUrl.toURL().toString());\n  }\n\n  @Test\n  public void issue_2583() throws MalformedURLException {\n    String original =\n        \"http://api.met.no/weatherapi/weathericon/1.1/?symbol=9;content_type=image/png\";\n\n    GlideUrl glideUrl = new GlideUrl(original);\n    assertThat(glideUrl.toURL().toString()).isEqualTo(original);\n    assertThat(glideUrl.toStringUrl()).isEqualTo(original);\n  }\n\n  @Test\n  public void testEquals() throws MalformedURLException {\n    Headers headers = mock(Headers.class);\n    Headers otherHeaders = mock(Headers.class);\n    String url = \"http://www.google.com\";\n    String otherUrl = \"http://mail.google.com\";\n    new EqualsTester()\n        .addEqualityGroup(\n            new GlideUrl(url),\n            new GlideUrl(url),\n            new GlideUrl(new URL(url)),\n            new GlideUrl(new URL(url)))\n        .addEqualityGroup(new GlideUrl(otherUrl), new GlideUrl(new URL(otherUrl)))\n        .addEqualityGroup(new GlideUrl(url, headers), new GlideUrl(new URL(url), headers))\n        .addEqualityGroup(new GlideUrl(url, otherHeaders), new GlideUrl(new URL(url), otherHeaders))\n        .testEquals();\n  }\n\n  @Test\n  public void issue_5444() throws MalformedURLException {\n    String original = \"http://[2600:1f13:37c:1400:ba21:7165:5fc7:736e]/\";\n    GlideUrl glideUrl = new GlideUrl(original);\n    assertThat(glideUrl.toURL().toString()).isEqualTo(original);\n    assertThat(glideUrl.toStringUrl()).isEqualTo(original);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/LazyHeadersTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.model.LazyHeaders.Builder;\nimport com.google.common.testing.EqualsTester;\nimport java.util.Map;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class LazyHeadersTest {\n  private static final String DEFAULT_USER_AGENT = \"default_user_agent\";\n  private static final String DEFAULT_USER_AGENT_PROPERTY = \"http.agent\";\n  private String initialUserAgent;\n\n  @Before\n  public void setUp() {\n    initialUserAgent = System.getProperty(DEFAULT_USER_AGENT_PROPERTY);\n    System.setProperty(DEFAULT_USER_AGENT_PROPERTY, DEFAULT_USER_AGENT);\n  }\n\n  @After\n  public void tearDown() {\n    if (initialUserAgent != null) {\n      System.setProperty(DEFAULT_USER_AGENT_PROPERTY, initialUserAgent);\n    }\n  }\n\n  // Tests for #2331.\n  @Test\n  public void getSanitizedUserAgent_withInvalidAgent_returnsAgentWithInvalidCharactersRemoved() {\n    String invalidUserAgent = \"Dalvik/2.1.0 (Linux; U; Android 5.0; P98 4G八核版(A8H8) Build/LRX21M)\";\n    String validUserAgent = \"Dalvik/2.1.0 (Linux; U; Android 5.0; P98 4G???(A8H8) Build/LRX21M)\";\n    System.setProperty(DEFAULT_USER_AGENT_PROPERTY, invalidUserAgent);\n    assertThat(LazyHeaders.Builder.getSanitizedUserAgent()).isEqualTo(validUserAgent);\n  }\n\n  @Test\n  public void getSanitizedUserAgent_withValidAgent_returnsUnmodifiedAgent() {\n    String validUserAgent = \"Dalvik/2.1.0 (Linux; U; Android 5.0; P98 4G(A8H8) Build/LRX21M)\";\n    System.setProperty(DEFAULT_USER_AGENT_PROPERTY, validUserAgent);\n    assertThat(LazyHeaders.Builder.getSanitizedUserAgent()).isEqualTo(validUserAgent);\n  }\n\n  @Test\n  public void getSanitizedUserAgent_withMissingAgent_returnsNull() {\n    System.clearProperty(DEFAULT_USER_AGENT_PROPERTY);\n    assertThat(LazyHeaders.Builder.getSanitizedUserAgent()).isNull();\n  }\n\n  @Test\n  public void getSanitizedUserAgent_withEmptyStringAgent_returnsEmptyString() {\n    String userAgent = \"\";\n    System.setProperty(DEFAULT_USER_AGENT_PROPERTY, userAgent);\n    assertThat(LazyHeaders.Builder.getSanitizedUserAgent()).isEqualTo(userAgent);\n  }\n\n  @Test\n  public void getSanitizedUserAgent_withWhitespace_returnsWhitespaceString() {\n    String userAgent = \"  \\t\";\n    System.setProperty(DEFAULT_USER_AGENT_PROPERTY, userAgent);\n    assertThat(LazyHeaders.Builder.getSanitizedUserAgent()).isEqualTo(userAgent);\n  }\n\n  @Test\n  public void testIncludesEagerHeaders() {\n    Map<String, String> headers = new Builder().addHeader(\"key\", \"value\").build().getHeaders();\n    assertThat(headers).containsEntry(\"key\", \"value\");\n  }\n\n  @Test\n  public void testIncludesLazyHeaders() {\n    LazyHeaderFactory factory = mock(LazyHeaderFactory.class);\n    when(factory.buildHeader()).thenReturn(\"value\");\n    Map<String, String> headers = new Builder().addHeader(\"key\", factory).build().getHeaders();\n\n    assertThat(headers).containsEntry(\"key\", \"value\");\n  }\n\n  @Test\n  public void testMultipleEagerValuesAreSeparatedByCommas() {\n    Map<String, String> headers =\n        new Builder().addHeader(\"key\", \"first\").addHeader(\"key\", \"second\").build().getHeaders();\n\n    assertThat(headers).containsEntry(\"key\", \"first,second\");\n  }\n\n  @Test\n  public void testMultipleLazyValuesAreSeparatedByCommas() {\n    LazyHeaderFactory first = mock(LazyHeaderFactory.class);\n    when(first.buildHeader()).thenReturn(\"first\");\n    LazyHeaderFactory second = mock(LazyHeaderFactory.class);\n    when(second.buildHeader()).thenReturn(\"second\");\n\n    Map<String, String> headers =\n        new Builder().addHeader(\"key\", first).addHeader(\"key\", second).build().getHeaders();\n    assertThat(headers).containsEntry(\"key\", \"first,second\");\n  }\n\n  @Test\n  public void testMixedEagerAndLazyValuesAreIncluded() {\n    LazyHeaderFactory factory = mock(LazyHeaderFactory.class);\n    when(factory.buildHeader()).thenReturn(\"first\");\n    Map<String, String> headers =\n        new Builder().addHeader(\"key\", factory).addHeader(\"key\", \"second\").build().getHeaders();\n\n    assertThat(headers).containsEntry(\"key\", \"first,second\");\n\n    headers =\n        new Builder().addHeader(\"key\", \"second\").addHeader(\"key\", factory).build().getHeaders();\n\n    assertThat(headers).containsEntry(\"key\", \"second,first\");\n  }\n\n  @Test\n  public void testCanAddMultipleKeys() {\n    LazyHeaderFactory factory = mock(LazyHeaderFactory.class);\n    when(factory.buildHeader()).thenReturn(\"lazy\");\n    Map<String, String> headers =\n        new Builder().addHeader(\"first\", factory).addHeader(\"second\", \"eager\").build().getHeaders();\n\n    assertThat(headers).containsEntry(\"first\", \"lazy\");\n    assertThat(headers).containsEntry(\"second\", \"eager\");\n  }\n\n  @Test\n  public void testUpdatingBuilderAfterBuildingDoesNotModifyOriginalHeaders() {\n    Builder builder = new Builder();\n    builder.addHeader(\"key\", \"firstValue\");\n    builder.addHeader(\"otherKey\", \"otherValue\");\n    LazyHeaders first = builder.build();\n\n    LazyHeaderFactory factory = mock(LazyHeaderFactory.class);\n    when(factory.buildHeader()).thenReturn(\"otherValue\");\n    builder.addHeader(\"key\", \"secondValue\");\n    builder.setHeader(\"otherKey\", factory);\n    LazyHeaders second = builder.build();\n\n    assertThat(first.getHeaders()).isNotEqualTo(second.getHeaders());\n\n    assertThat(first.getHeaders()).containsEntry(\"key\", \"firstValue\");\n    assertThat(first.getHeaders()).containsEntry(\"otherKey\", \"otherValue\");\n\n    assertThat(second.getHeaders()).containsEntry(\"key\", \"firstValue,secondValue\");\n    assertThat(second.getHeaders()).containsEntry(\"otherKey\", \"otherValue\");\n  }\n\n  @Test\n  public void testSetHeaderReplacesExistingHeaders() {\n    Builder builder = new Builder();\n    builder.addHeader(\"key\", \"first\").addHeader(\"key\", \"second\").setHeader(\"key\", \"third\");\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"key\", \"third\");\n  }\n\n  @Test\n  public void testSetHeaderWithNullStringRemovesExistingHeader() {\n    Builder builder = new Builder();\n    builder.addHeader(\"key\", \"first\").addHeader(\"key\", \"second\").setHeader(\"key\", (String) null);\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).doesNotContainKey(\"key\");\n  }\n\n  @Test\n  public void testSetHeaderWithNullLazyHeaderFactoryRemovesExistingHeader() {\n    Builder builder = new Builder();\n    builder\n        .addHeader(\"key\", \"first\")\n        .addHeader(\"key\", \"second\")\n        .setHeader(\"key\", (LazyHeaderFactory) null);\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).doesNotContainKey(\"key\");\n  }\n\n  @Test\n  public void testAddingEncodingHeaderReplacesDefaultThenAppends() {\n    Builder builder = new Builder();\n    builder.addHeader(\"Accept-Encoding\", \"false\");\n\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"Accept-Encoding\", \"false\");\n\n    builder.addHeader(\"Accept-Encoding\", \"true\");\n    headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"Accept-Encoding\", \"false,true\");\n  }\n\n  @Test\n  public void testRemovingAndAddingEncodingHeaderReplacesDefaultThenAppends() {\n    Builder builder = new Builder();\n    builder.setHeader(\"Accept-Encoding\", (String) null);\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).doesNotContainKey(\"Accept-Encoding\");\n\n    builder.addHeader(\"Accept-Encoding\", \"false\");\n    headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"Accept-Encoding\", \"false\");\n\n    builder.addHeader(\"Accept-Encoding\", \"true\");\n    headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"Accept-Encoding\", \"false,true\");\n  }\n\n  @Test\n  public void testAddingUserAgentHeaderReplacesDefaultThenAppends() {\n    Builder builder = new Builder();\n    builder.addHeader(\"User-Agent\", \"false\");\n\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"User-Agent\", \"false\");\n\n    builder.addHeader(\"User-Agent\", \"true\");\n    headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"User-Agent\", \"false,true\");\n  }\n\n  @Test\n  public void testRemovingAndAddingUserAgentHeaderReplacesDefaultThenAppends() {\n    Builder builder = new Builder();\n    builder.setHeader(\"User-Agent\", (String) null);\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).doesNotContainKey(\"User-Agent\");\n\n    builder.addHeader(\"User-Agent\", \"false\");\n    headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"User-Agent\", \"false\");\n\n    builder.addHeader(\"User-Agent\", \"true\");\n    headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"User-Agent\", \"false,true\");\n  }\n\n  @Test\n  public void testKeyNotIncludedWithFactoryThatReturnsNullValue() {\n    Builder builder = new Builder();\n    builder.setHeader(\n        \"test\",\n        new LazyHeaderFactory() {\n          @Nullable\n          @Override\n          public String buildHeader() {\n            return null;\n          }\n        });\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).doesNotContainKey(\"test\");\n  }\n\n  @Test\n  public void testKeyNotIncludedWithFactoryThatReturnsEmptyValue() {\n    Builder builder = new Builder();\n    builder.setHeader(\n        \"test\",\n        new LazyHeaderFactory() {\n          @Nullable\n          @Override\n          public String buildHeader() {\n            return \"\";\n          }\n        });\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).doesNotContainKey(\"test\");\n  }\n\n  @Test\n  public void testKeyIncludedWithOneFactoryThatReturnsNullAndOneFactoryThatDoesNotReturnNull() {\n    Builder builder = new Builder();\n    builder.addHeader(\n        \"test\",\n        new LazyHeaderFactory() {\n          @Nullable\n          @Override\n          public String buildHeader() {\n            return null;\n          }\n        });\n    builder.addHeader(\n        \"test\",\n        new LazyHeaderFactory() {\n          @Nullable\n          @Override\n          public String buildHeader() {\n            return \"value\";\n          }\n        });\n    LazyHeaders headers = builder.build();\n    assertThat(headers.getHeaders()).containsEntry(\"test\", \"value\");\n  }\n\n  @Test\n  public void testEquals() {\n    LazyHeaderFactory firstLazyFactory = mock(LazyHeaderFactory.class);\n    LazyHeaderFactory secondLazyFactory = mock(LazyHeaderFactory.class);\n    new EqualsTester()\n        .addEqualityGroup(new Builder().build(), new Builder().build())\n        .addEqualityGroup(\n            new Builder().addHeader(\"key\", \"value\").build(),\n            new Builder().addHeader(\"key\", \"value\").build())\n        .addEqualityGroup(new Builder().addHeader(\"key\", \"value\").addHeader(\"key\", \"value\").build())\n        .addEqualityGroup(\n            new Builder().addHeader(\"key\", firstLazyFactory).build(),\n            new Builder().addHeader(\"key\", firstLazyFactory).build())\n        .addEqualityGroup(\n            new Builder()\n                .addHeader(\"key\", firstLazyFactory)\n                .addHeader(\"key\", firstLazyFactory)\n                .build())\n        .addEqualityGroup(\n            new Builder()\n                .addHeader(\"firstKey\", \"value\")\n                .addHeader(\"secondKey\", firstLazyFactory)\n                .build(),\n            new Builder()\n                .addHeader(\"secondKey\", firstLazyFactory)\n                .addHeader(\"firstKey\", \"value\")\n                .build())\n        .addEqualityGroup(new Builder().addHeader(\"key\", \"secondValue\"))\n        .addEqualityGroup(new Builder().addHeader(\"secondKey\", \"value\"))\n        .addEqualityGroup(new Builder().addHeader(\"key\", secondLazyFactory))\n        .addEqualityGroup(new Builder().addHeader(\"secondKey\", firstLazyFactory))\n        .addEqualityGroup(\n            new Builder()\n                .addHeader(\"firstKey\", \"firstValue\")\n                .addHeader(\"secondKey\", \"secondValue\")\n                .build(),\n            new Builder()\n                .addHeader(\"firstKey\", \"firstValue\")\n                .addHeader(\"secondKey\", \"secondValue\")\n                .build(),\n            new Builder()\n                .addHeader(\"secondKey\", \"secondValue\")\n                .addHeader(\"firstKey\", \"firstValue\")\n                .build())\n        .addEqualityGroup(\n            new Builder()\n                .addHeader(\"firstKey\", firstLazyFactory)\n                .addHeader(\"secondKey\", secondLazyFactory)\n                .build(),\n            new Builder()\n                .addHeader(\"firstKey\", firstLazyFactory)\n                .addHeader(\"secondKey\", secondLazyFactory)\n                .build(),\n            new Builder()\n                .addHeader(\"secondKey\", secondLazyFactory)\n                .addHeader(\"firstKey\", firstLazyFactory)\n                .build())\n        .testEquals();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/ModelCacheTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static org.junit.Assert.assertEquals;\n\nimport com.google.common.testing.EqualsTester;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class ModelCacheTest {\n\n  private ModelCache<Object, Object> cache;\n\n  @Before\n  public void setUp() {\n    cache = new ModelCache<>(10);\n  }\n\n  @Test\n  public void testModelKeyEquivalence() {\n    new EqualsTester()\n        .addEqualityGroup(\n            ModelCache.ModelKey.get(14f, 100, 200), ModelCache.ModelKey.get(14f, 100, 200))\n        .addEqualityGroup(ModelCache.ModelKey.get(13f, 100, 200))\n        .addEqualityGroup(ModelCache.ModelKey.get(14f, 200, 200))\n        .addEqualityGroup(ModelCache.ModelKey.get(14f, 100, 300))\n        .testEquals();\n  }\n\n  @Test\n  public void testCanSetAndGetModel() {\n    Object model = new Object();\n    int width = 10;\n    int height = 20;\n    Object result = new Object();\n    cache.put(model, width, height, result);\n    assertEquals(result, cache.get(model, width, height));\n  }\n\n  @Test\n  public void testCanSetAndGetMultipleResultsWithDifferentDimensionsForSameObject() {\n    Object model = new Object();\n    int firstWidth = 10;\n    int firstHeight = 20;\n    Object firstResult = new Object();\n    int secondWidth = 30;\n    int secondHeight = 40;\n    Object secondResult = new Object();\n\n    cache.put(model, firstWidth, firstHeight, firstResult);\n    cache.put(model, secondWidth, secondHeight, secondResult);\n\n    assertEquals(firstResult, cache.get(model, firstWidth, firstHeight));\n    assertEquals(secondResult, cache.get(model, secondWidth, secondHeight));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/ModelLoaderRegistryTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Registry.NoModelLoaderAvailableException;\nimport com.bumptech.glide.util.pool.FactoryPools;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class ModelLoaderRegistryTest {\n  private static final String MOCK_MODEL_LOADER_NAME = \"MockModelLoader\";\n\n  private ModelLoaderRegistry registry;\n\n  @Before\n  public void setUp() {\n    registry = new ModelLoaderRegistry(FactoryPools.<Throwable>threadSafeList());\n  }\n\n  @Test\n  public void getModelLoaders_withNoRegisteredModelLoader_throws() {\n    final Object model = new Object();\n    NoModelLoaderAvailableException thrown =\n        assertThrows(\n            NoModelLoaderAvailableException.class,\n            new ThrowingRunnable() {\n              @Override\n              public void run() {\n                registry.getModelLoaders(model);\n              }\n            });\n\n    assertThat(thrown)\n        .hasMessageThat()\n        .contains(\n            \"Failed to find any ModelLoaders registered for model class: \" + model.getClass());\n  }\n\n  @Test\n  public void getModelLoaders_withRegisteredModelLoader_thatDoesNotHandleModelInstance_throws() {\n    final Object model = new Object();\n    final ModelLoader<Object, Object> modelLoader = mockModelLoader();\n    when(modelLoader.handles(model)).thenReturn(false);\n    appendModelLoader(modelLoader);\n\n    NoModelLoaderAvailableException thrown =\n        assertThrows(\n            NoModelLoaderAvailableException.class,\n            new ThrowingRunnable() {\n              @Override\n              public void run() {\n                registry.getModelLoaders(model);\n              }\n            });\n\n    assertThat(thrown)\n        .hasMessageThat()\n        .contains(\n            \"Found ModelLoaders for model class: [MockModelLoader], but none that handle this\"\n                + \" specific model instance: java.lang.Object\");\n  }\n\n  @Test\n  public void getModelLoaders_withRegisteredModelLoader_handlesModel_returnsModelLoader() {\n    final Object model = new Object();\n    final ModelLoader<Object, Object> modelLoader = mockModelLoader();\n    when(modelLoader.handles(model)).thenReturn(true);\n    appendModelLoader(modelLoader);\n\n    assertThat(registry.getModelLoaders(model)).containsExactly(modelLoader);\n  }\n\n  @Test\n  public void\n      getModelLoaders_withRegisteredModelLoaders_onlyOneHandlesModel_returnsHandlingModelLoader() {\n    final Object model = new Object();\n\n    ModelLoader<Object, Object> handlingModelLoader = mockModelLoader();\n    when(handlingModelLoader.handles(model)).thenReturn(true);\n    appendModelLoader(handlingModelLoader);\n\n    ModelLoader<Object, Object> notHandlingModelLoader = mockModelLoader();\n    when(notHandlingModelLoader.handles(model)).thenReturn(false);\n    appendModelLoader(notHandlingModelLoader);\n\n    assertThat(registry.getModelLoaders(model)).containsExactly(handlingModelLoader);\n  }\n\n  private void appendModelLoader(final ModelLoader<Object, Object> modelLoader) {\n    registry.append(\n        Object.class,\n        Object.class,\n        new ModelLoaderFactory<Object, Object>() {\n          @NonNull\n          @Override\n          public ModelLoader<Object, Object> build(@NonNull MultiModelLoaderFactory multiFactory) {\n            return modelLoader;\n          }\n\n          @Override\n          public void teardown() {}\n        });\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static ModelLoader<Object, Object> mockModelLoader() {\n    return mock(ModelLoader.class, MOCK_MODEL_LOADER_NAME);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/MultiModelLoaderFactoryTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport androidx.core.util.Pools.Pool;\nimport com.bumptech.glide.Registry.NoModelLoaderAvailableException;\nimport com.bumptech.glide.tests.Util;\nimport com.bumptech.glide.util.pool.FactoryPools;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n// containsExactly produces a spurious warning.\n@SuppressWarnings(\"ResultOfMethodCallIgnored\")\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class MultiModelLoaderFactoryTest {\n  @Mock private ModelLoaderFactory<String, String> firstFactory;\n  @Mock private ModelLoader<String, String> firstModelLoader;\n  @Mock private MultiModelLoaderFactory.Factory multiModelLoaderFactory;\n  @Mock private ModelLoaderFactory<String, String> secondFactory;\n  @Mock private ModelLoader<String, String> secondModelLoader;\n\n  private Pool<List<Throwable>> throwableListPool;\n  private MultiModelLoaderFactory multiFactory;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    throwableListPool = FactoryPools.threadSafeList();\n\n    multiFactory = new MultiModelLoaderFactory(throwableListPool, multiModelLoaderFactory);\n    when(firstFactory.build(eq(multiFactory))).thenReturn(firstModelLoader);\n    when(secondFactory.build(eq(multiFactory))).thenReturn(secondModelLoader);\n  }\n\n  @Test\n  public void testAppend_addsModelLoaderForModelClass() {\n    multiFactory.append(String.class, String.class, firstFactory);\n\n    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);\n    assertThat(modelLoaders).containsExactly(firstModelLoader);\n  }\n\n  @Test\n  public void testAppend_addsModelLoaderForModelAndDataClass() {\n    multiFactory.append(String.class, String.class, firstFactory);\n\n    ModelLoader<String, String> modelLoader = multiFactory.build(String.class, String.class);\n    assertThat(modelLoader).isEqualTo(firstModelLoader);\n  }\n\n  @Test\n  public void testPrepend_addsModelLoaderForModelClass() {\n    multiFactory.prepend(String.class, String.class, firstFactory);\n\n    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);\n    assertThat(modelLoaders).containsExactly(firstModelLoader);\n  }\n\n  @Test\n  public void testPrepend_addsModelLoaderForModelAndDataClass() {\n    multiFactory.prepend(String.class, String.class, firstFactory);\n\n    ModelLoader<String, String> modelLoader = multiFactory.build(String.class, String.class);\n    assertThat(modelLoader).isEqualTo(firstModelLoader);\n  }\n\n  @Test\n  public void testReplace_addsModelLoaderForModelClass() {\n    multiFactory.replace(String.class, String.class, firstFactory);\n\n    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);\n    assertThat(modelLoaders).containsExactly(firstModelLoader);\n  }\n\n  @Test\n  public void testReplace_addsModelLoaderForModelAndDataClasses() {\n    multiFactory.replace(String.class, String.class, firstFactory);\n\n    ModelLoader<String, String> modelLoader = multiFactory.build(String.class, String.class);\n    assertThat(modelLoader).isEqualTo(firstModelLoader);\n  }\n\n  @Test\n  public void testReplace_returnsPreviouslyRegisteredFactories_withModelAndDataClasses() {\n    ModelLoaderFactory<String, String> firstOtherFactory = mockFactory();\n    ModelLoaderFactory<String, String> secondOtherFactory = mockFactory();\n    multiFactory.append(String.class, String.class, firstOtherFactory);\n    multiFactory.append(String.class, String.class, secondOtherFactory);\n\n    List<ModelLoaderFactory<? extends String, ? extends String>> removed =\n        multiFactory.replace(String.class, String.class, firstFactory);\n    assertThat(removed).containsExactly(firstOtherFactory, secondOtherFactory);\n  }\n\n  @Test\n  public void testReplace_removesPreviouslyRegisteredFactories_withModelAndDataClasses() {\n    appendFactoryFor(String.class, String.class);\n    appendFactoryFor(String.class, String.class);\n\n    multiFactory.replace(String.class, String.class, firstFactory);\n\n    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);\n    assertThat(modelLoaders).containsExactly(firstModelLoader);\n  }\n\n  @Test\n  public void testRemove_returnsPreviouslyRegisteredFactories_withModelAndDataClasses() {\n    ModelLoaderFactory<String, String> other = mockFactory();\n    multiFactory.append(String.class, String.class, other);\n    multiFactory.append(String.class, String.class, firstFactory);\n\n    List<ModelLoaderFactory<? extends String, ? extends String>> removed =\n        multiFactory.remove(String.class, String.class);\n    assertThat(removed).containsExactly(firstFactory, other);\n  }\n\n  @Test\n  public void testRemove_removesPreviouslyRegisteredFactories_withModelAndDataClasses() {\n    appendFactoryFor(String.class, String.class);\n    appendFactoryFor(String.class, String.class);\n\n    multiFactory.remove(String.class, String.class);\n\n    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);\n    assertThat(modelLoaders).isEmpty();\n  }\n\n  @Test\n  public void testBuild_withModelClass_returnsMultipleModelLoaders_ofGivenModelAndDataClasses() {\n    ModelLoader<String, String> otherLoader = appendFactoryFor(String.class, String.class);\n    multiFactory.append(String.class, String.class, firstFactory);\n\n    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);\n    assertThat(modelLoaders).containsExactly(otherLoader, firstModelLoader);\n  }\n\n  @Test\n  public void\n      testBuild_withModelClass_returnsMultipleModelLoaders_ofGivenModelClassWithDifferentDataClasses() {\n    ModelLoader<String, Integer> otherLoader = appendFactoryFor(String.class, Integer.class);\n    multiFactory.append(String.class, String.class, firstFactory);\n\n    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);\n    assertThat(modelLoaders).containsExactly(otherLoader, firstModelLoader);\n  }\n\n  @SuppressWarnings(\"TruthIncompatibleType\")\n  @Test\n  public void testBuild_withModelClass_excludesModelLoadersForOtherModelClasses() {\n    multiFactory.append(String.class, String.class, firstFactory);\n    List<ModelLoader<Integer, ?>> modelLoaders = multiFactory.build(Integer.class);\n    assertThat(modelLoaders)\n        .doesNotContain(\n            /* expected: ModelLoader<Integer, ?>, actual: ModelLoader<String, String> */ firstModelLoader);\n  }\n\n  @Test\n  public void\n      testBuild_withModelAndDataClasses_returnsMultipleModelLoaders_ofGivenModelAndDataClasses() {\n    ModelLoader<String, String> otherLoader = appendFactoryFor(String.class, String.class);\n    multiFactory.append(String.class, String.class, firstFactory);\n\n    List<ModelLoader<String, String>> modelLoaders = buildModelLoaders(String.class, String.class);\n    assertThat(modelLoaders).containsExactly(otherLoader, firstModelLoader);\n  }\n\n  @Test\n  public void testBuild_withModelAndDataClasses_excludesModelLoadersForOtherDataClasses() {\n    multiFactory.append(String.class, String.class, firstFactory);\n\n    assertThrows(\n        NoModelLoaderAvailableException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            multiFactory.build(String.class, Integer.class);\n          }\n        });\n  }\n\n  @Test\n  public void testBuild_withModelAndDataClasses_excludesModelLoadersForOtherModelClasses() {\n    multiFactory.append(String.class, String.class, firstFactory);\n\n    assertThrows(\n        NoModelLoaderAvailableException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            multiFactory.build(Integer.class, String.class);\n          }\n        });\n  }\n\n  @Test\n  public void testBuild_withModelClass_doesNotMatchSubclassesOfModelClass() {\n    ModelLoader<String, Object> subclass = appendFactoryFor(String.class, Object.class);\n    List<ModelLoader<Object, ?>> modelLoaders = multiFactory.build(Object.class);\n    assertThat(modelLoaders).doesNotContain(subclass);\n  }\n\n  @Test\n  public void testBuild_withModelClass_matchesSuperclassesOfModelClass() {\n    ModelLoader<Object, Object> superclass = appendFactoryFor(Object.class, Object.class);\n    List<ModelLoader<String, ?>> modelLoaders = multiFactory.build(String.class);\n    assertThat(modelLoaders).contains(superclass);\n  }\n\n  @Test\n  public void testBuild_withModelAndDataClass_doesNotMatchSubclassesOfModelClass() {\n    appendFactoryFor(String.class, Object.class);\n\n    assertThrows(\n        NoModelLoaderAvailableException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            multiFactory.build(Object.class, Object.class);\n          }\n        });\n  }\n\n  @Test\n  public void testBuild_withModelAndDataClass_doesNotMatchSubclassesOfDataClass() {\n    appendFactoryFor(Object.class, String.class);\n    assertThrows(\n        NoModelLoaderAvailableException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            multiFactory.build(Object.class, Object.class);\n          }\n        });\n  }\n\n  @Test\n  public void testBuild_withModelAndDataClass_doesMatchSuperclassesOfModelClass() {\n    ModelLoader<Object, Object> firstSuperClass = appendFactoryFor(Object.class, Object.class);\n    ModelLoader<Object, Object> secondSuperClass = appendFactoryFor(Object.class, Object.class);\n    List<ModelLoader<String, Object>> modelLoaders = buildModelLoaders(String.class, Object.class);\n    assertThat(modelLoaders).containsExactly(firstSuperClass, secondSuperClass);\n  }\n\n  @Test\n  public void testBuild_withModelAndDataClass_matchesSuperclassesOfDataClass() {\n    ModelLoader<Object, Object> firstSuperClass = appendFactoryFor(Object.class, Object.class);\n    ModelLoader<Object, Object> secondSuperClass = appendFactoryFor(Object.class, Object.class);\n    List<ModelLoader<Object, String>> modelLoaders = buildModelLoaders(Object.class, String.class);\n    assertThat(modelLoaders).containsExactly(firstSuperClass, secondSuperClass);\n  }\n\n  @Test\n  public void testBuild_withModelAndDataClass_matchesSuperclassOfModelAndDataClass() {\n    ModelLoader<Object, Object> firstSuperclass = appendFactoryFor(Object.class, Object.class);\n    ModelLoader<Object, Object> secondSuperclass = appendFactoryFor(Object.class, Object.class);\n    List<ModelLoader<String, String>> modelLoaders = buildModelLoaders(String.class, String.class);\n    assertThat(modelLoaders).containsExactly(firstSuperclass, secondSuperclass);\n  }\n\n  @Test\n  public void testBuild_respectsAppendOrder() {\n    ModelLoader<String, String> first = appendFactoryFor(String.class, String.class);\n    ModelLoader<String, String> second = appendFactoryFor(String.class, String.class);\n    ModelLoader<String, String> third = appendFactoryFor(String.class, String.class);\n    List<ModelLoader<String, String>> modelLoaders = buildModelLoaders(String.class, String.class);\n    assertThat(modelLoaders).containsExactly(first, second, third).inOrder();\n  }\n\n  @Test\n  public void testBuild_respectsPrependOrder() {\n    ModelLoader<String, String> first = prependFactoryFor(String.class, String.class);\n    ModelLoader<String, String> second = prependFactoryFor(String.class, String.class);\n    ModelLoader<String, String> third = prependFactoryFor(String.class, String.class);\n    List<ModelLoader<String, String>> modelLoaders = buildModelLoaders(String.class, String.class);\n    assertThat(modelLoaders).containsExactly(third, second, first).inOrder();\n  }\n\n  private <X, Y> List<ModelLoader<X, Y>> buildModelLoaders(\n      Class<X> modelClass, Class<Y> dataClass) {\n    ArgumentCaptor<List<ModelLoader<X, Y>>> captor = Util.cast(ArgumentCaptor.forClass(List.class));\n    multiFactory.build(modelClass, dataClass);\n    verify(multiModelLoaderFactory).build(captor.capture(), eq(throwableListPool));\n\n    List<ModelLoader<X, Y>> captured = captor.getValue();\n    List<ModelLoader<X, Y>> result = new ArrayList<>(captured.size());\n    result.addAll(captured);\n    return result;\n  }\n\n  private <X, Y> ModelLoader<X, Y> appendFactoryFor(Class<X> modelClass, Class<Y> dataClass) {\n    return registerFactoryFor(modelClass, dataClass, true /*append*/);\n  }\n\n  private <X, Y> ModelLoader<X, Y> prependFactoryFor(Class<X> modelClass, Class<Y> dataClass) {\n    return registerFactoryFor(modelClass, dataClass, false /*append*/);\n  }\n\n  private <X, Y> ModelLoader<X, Y> registerFactoryFor(\n      Class<X> modelClass, Class<Y> dataClass, boolean append) {\n    ModelLoaderFactory<X, Y> factory = mockFactory();\n    @SuppressWarnings(\"unchecked\")\n    ModelLoader<X, Y> loader = mock(ModelLoader.class);\n    when(factory.build(eq(multiFactory))).thenReturn(loader);\n    if (append) {\n      multiFactory.append(modelClass, dataClass, factory);\n    } else {\n      multiFactory.prepend(modelClass, dataClass, factory);\n    }\n    return loader;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static <X, Y> ModelLoaderFactory<X, Y> mockFactory() {\n    return mock(ModelLoaderFactory.class);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/ResourceLoaderTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.util.Preconditions;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n/** Tests for the {@link com.bumptech.glide.load.model.ResourceLoader} class. */\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ResourceLoaderTest {\n\n  @Mock private ModelLoader<Uri, Object> uriLoader;\n  @Mock private DataFetcher<Object> fetcher;\n  @Mock private Key key;\n  private Options options;\n\n  private ResourceLoader<Object> loader;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    options = new Options();\n\n    loader =\n        new ResourceLoader<>(ApplicationProvider.getApplicationContext().getResources(), uriLoader);\n  }\n\n  @Test\n  public void testCanHandleId() {\n    int id = android.R.drawable.star_off;\n    Uri contentUri = Uri.parse(\"android.resource://android/\" + String.valueOf(id));\n    when(uriLoader.buildLoadData(eq(contentUri), anyInt(), anyInt(), any(Options.class)))\n        .thenReturn(new ModelLoader.LoadData<>(key, fetcher));\n\n    assertTrue(loader.handles(id));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(loader.buildLoadData(id, 100, 100, new Options())).fetcher);\n  }\n\n  @Test\n  public void testDoesNotThrowOnInvalidOrMissingId() {\n    assertThat(loader.buildLoadData(1234, 0, 0, options)).isNull();\n    verify(uriLoader, never())\n        .buildLoadData(any(Uri.class), anyInt(), anyInt(), any(Options.class));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/StreamEncoderTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class StreamEncoderTest {\n  private StreamEncoder encoder;\n  private File file;\n\n  @Before\n  public void setUp() {\n    encoder = new StreamEncoder(new LruArrayPool());\n    file = new File(ApplicationProvider.getApplicationContext().getCacheDir(), \"test\");\n  }\n\n  @After\n  public void tearDown() {\n    // GC before delete() to release files on Windows (https://stackoverflow.com/a/4213208/253468)\n    System.gc();\n    if (!file.delete()) {\n      throw new IllegalStateException(\"Failed to delete: \" + file);\n    }\n  }\n\n  @Test\n  public void testWritesDataFromInputStreamToOutputStream() throws IOException {\n    String fakeData = \"SomeRandomFakeData\";\n    ByteArrayInputStream is = new ByteArrayInputStream(fakeData.getBytes(\"UTF-8\"));\n    encoder.encode(is, file, new Options());\n\n    byte[] data = ByteBufferUtil.toBytes(ByteBufferUtil.fromFile(file));\n\n    assertEquals(fakeData, new String(data, \"UTF-8\"));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/StringLoaderTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.Assume.assumeTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\nimport android.net.Uri;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.tests.Util;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.File;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n/** Tests for the {@link com.bumptech.glide.load.model.StringLoader} class. */\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class StringLoaderTest {\n  // Not a magic number, just an arbitrary non zero value.\n  private static final int IMAGE_SIDE = 100;\n\n  @Mock private ModelLoader<Uri, Object> uriLoader;\n  @Mock private DataFetcher<Object> fetcher;\n  @Mock private Key key;\n\n  private StringLoader<Object> loader;\n  private Options options;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    options = new Options();\n    when(uriLoader.handles(any(Uri.class))).thenReturn(true);\n    loader = new StringLoader<>(uriLoader);\n  }\n\n  @Test\n  public void testHandlesPaths() {\n    // TODO fix drive letter parsing somehow\n    assumeTrue(\"it will fail with schema being the drive letter (C:\\\\... -> C)\", !Util.isWindows());\n\n    File f = ApplicationProvider.getApplicationContext().getCacheDir();\n    Uri expected = Uri.fromFile(f);\n    when(uriLoader.buildLoadData(eq(expected), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(key, fetcher));\n\n    assertTrue(loader.handles(f.getAbsolutePath()));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(\n                loader.buildLoadData(f.getAbsolutePath(), IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testCanHandleComplexFilePaths() {\n    String testPath =\n        \"/storage/emulated/0/DCIM/Camera/IMG_20140520_100001:nopm:.jpg,mimeType=image/jpeg,\"\n            + \"2448x3264,orientation=0,date=Tue\";\n    Uri expected = Uri.fromFile(new File(testPath));\n    when(uriLoader.buildLoadData(eq(expected), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(key, fetcher));\n\n    assertTrue(loader.handles(testPath));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(loader.buildLoadData(testPath, IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testHandlesFileUris() {\n    File f = ApplicationProvider.getApplicationContext().getCacheDir();\n\n    Uri expected = Uri.fromFile(f);\n    when(uriLoader.buildLoadData(eq(expected), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(key, fetcher));\n\n    assertTrue(loader.handles(f.getAbsolutePath()));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(\n                loader.buildLoadData(expected.toString(), IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testHandlesResourceUris() {\n    Uri resourceUri = Uri.parse(\"android.resource://com.bumptech.glide.tests/raw/ic_launcher\");\n\n    when(uriLoader.buildLoadData(eq(resourceUri), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(key, fetcher));\n\n    assertTrue(loader.handles(resourceUri.toString()));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(\n                loader.buildLoadData(resourceUri.toString(), IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testHandlesHttp() {\n    String url = \"http://www.google.com\";\n\n    Uri expected = Uri.parse(url);\n    when(uriLoader.buildLoadData(eq(expected), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(key, fetcher));\n\n    assertTrue(loader.handles(url));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(loader.buildLoadData(url, IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testHandlesHttps() {\n    String url = \"https://www.google.com\";\n\n    Uri expected = Uri.parse(url);\n    when(uriLoader.buildLoadData(eq(expected), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(key, fetcher));\n\n    assertTrue(loader.handles(url));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(loader.buildLoadData(url, IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testHandlesContent() {\n    String content = \"content://com.bumptech.glide\";\n\n    Uri expected = Uri.parse(content);\n    when(uriLoader.buildLoadData(eq(expected), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(key, fetcher));\n\n    assertTrue(loader.handles(content));\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(loader.buildLoadData(content, IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testGetResourceFetcher_withEmptyString_returnsNull() {\n    assertThat(loader.buildLoadData(\"\", IMAGE_SIDE, IMAGE_SIDE, options)).isNull();\n    assertThat(loader.buildLoadData(\"    \", IMAGE_SIDE, IMAGE_SIDE, options)).isNull();\n    assertThat(loader.buildLoadData(\"  \\n\", IMAGE_SIDE, IMAGE_SIDE, options)).isNull();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/UriLoaderTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\nimport android.net.Uri;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.File;\nimport java.io.IOException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n/** Tests for the {@link UriLoader} class. */\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class UriLoaderTest {\n  // Not a magic number, just arbitrary non zero.\n  private static final int IMAGE_SIDE = 120;\n\n  @Mock private DataFetcher<Object> localUriFetcher;\n  @Mock private UriLoader.LocalUriFetcherFactory<Object> factory;\n  private UriLoader<Object> loader;\n  private Options options;\n\n  @Before\n  public void setUp() throws Exception {\n    MockitoAnnotations.initMocks(this);\n\n    options = new Options();\n    loader = new UriLoader<>(factory);\n  }\n\n  @Test\n  public void testHandlesFileUris() throws IOException {\n    Uri fileUri = Uri.fromFile(new File(\"f\"));\n    when(factory.build(eq(fileUri))).thenReturn(localUriFetcher);\n\n    assertTrue(loader.handles(fileUri));\n    assertEquals(\n        localUriFetcher,\n        Preconditions.checkNotNull(loader.buildLoadData(fileUri, IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testHandlesContentUris() {\n    Uri contentUri = Uri.parse(\"content://com.bumptech.glide\");\n    when(factory.build(eq(contentUri))).thenReturn(localUriFetcher);\n\n    assertTrue(loader.handles(contentUri));\n    assertEquals(\n        localUriFetcher,\n        Preconditions.checkNotNull(\n                loader.buildLoadData(contentUri, IMAGE_SIDE, IMAGE_SIDE, options))\n            .fetcher);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/UrlUriLoaderTest.java",
    "content": "package com.bumptech.glide.load.model;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\n\nimport android.net.Uri;\nimport com.bumptech.glide.load.Options;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class UrlUriLoaderTest {\n  private static final int IMAGE_SIDE = 100;\n  private static final Options OPTIONS = new Options();\n\n  @Mock private ModelLoader<GlideUrl, InputStream> urlLoader;\n  private UrlUriLoader<InputStream> loader;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    loader = new UrlUriLoader<>(urlLoader);\n  }\n\n  @Test\n  public void testHandlesHttpUris() throws MalformedURLException {\n    Uri httpUri = Uri.parse(\"http://www.google.com\");\n    loader.buildLoadData(httpUri, IMAGE_SIDE, IMAGE_SIDE, OPTIONS);\n\n    assertTrue(loader.handles(httpUri));\n    verify(urlLoader)\n        .buildLoadData(\n            eq(new GlideUrl(httpUri.toString())), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(OPTIONS));\n  }\n\n  @Test\n  public void testHandlesHttpsUris() throws MalformedURLException {\n    Uri httpsUri = Uri.parse(\"https://www.google.com\");\n    loader.buildLoadData(httpsUri, IMAGE_SIDE, IMAGE_SIDE, OPTIONS);\n\n    assertTrue(loader.handles(httpsUri));\n    verify(urlLoader)\n        .buildLoadData(\n            eq(new GlideUrl(httpsUri.toString())), eq(IMAGE_SIDE), eq(IMAGE_SIDE), eq(OPTIONS));\n  }\n\n  // Test for https://github.com/bumptech/glide/issues/71.\n  @Test\n  public void testHandlesMostlyInvalidHttpUris() {\n    Uri mostlyInvalidHttpUri =\n        Uri.parse(\n            \"http://myserver_url.com:80http://myserver_url.com/webapp/images/no_image.png\"\n                + \"?size=100\");\n\n    assertTrue(loader.handles(mostlyInvalidHttpUri));\n    loader.buildLoadData(mostlyInvalidHttpUri, IMAGE_SIDE, IMAGE_SIDE, OPTIONS);\n    verify(urlLoader)\n        .buildLoadData(\n            eq(new GlideUrl(mostlyInvalidHttpUri.toString())),\n            eq(IMAGE_SIDE),\n            eq(IMAGE_SIDE),\n            eq(OPTIONS));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/stream/BaseGlideUrlLoaderTest.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelCache;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BaseGlideUrlLoaderTest {\n\n  @Mock private ModelCache<Object, GlideUrl> modelCache;\n  @Mock private ModelLoader<GlideUrl, InputStream> wrapped;\n  @Mock private DataFetcher<InputStream> fetcher;\n  private TestLoader urlLoader;\n  private Options options;\n\n  @SuppressWarnings(\"unchecked\")\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    options = new Options();\n    urlLoader = new TestLoader(wrapped, modelCache);\n  }\n\n  @Test\n  public void testReturnsNullIfUrlIsNull() {\n    urlLoader.resultUrl = null;\n    assertNull(urlLoader.buildLoadData(new Object(), 100, 100, options));\n  }\n\n  @Test\n  public void testReturnsNullIfUrlIsEmpty() {\n    urlLoader.resultUrl = \"    \";\n    assertNull(urlLoader.buildLoadData(new Object(), 100, 100, options));\n  }\n\n  @Test\n  public void testReturnsUrlFromCacheIfPresent() {\n    Object model = new Object();\n    int width = 100;\n    int height = 200;\n    GlideUrl expectedUrl = mock(GlideUrl.class);\n    when(modelCache.get(eq(model), eq(width), eq(height))).thenReturn(expectedUrl);\n\n    when(wrapped.buildLoadData(eq(expectedUrl), eq(width), eq(height), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), fetcher));\n\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(urlLoader.buildLoadData(model, width, height, options)).fetcher);\n  }\n\n  @Test\n  public void testBuildsNewUrlIfNotPresentInCache() {\n    int width = 10;\n    int height = 11;\n\n    urlLoader.resultUrl = \"fakeUrl\";\n    when(wrapped.buildLoadData(any(GlideUrl.class), eq(width), eq(height), eq(options)))\n        .thenAnswer(\n            new Answer<ModelLoader.LoadData<InputStream>>() {\n              @Override\n              public ModelLoader.LoadData<InputStream> answer(InvocationOnMock invocationOnMock) {\n                GlideUrl glideUrl = (GlideUrl) invocationOnMock.getArguments()[0];\n                assertEquals(urlLoader.resultUrl, glideUrl.toStringUrl());\n                return new ModelLoader.LoadData<>(mock(Key.class), fetcher);\n              }\n            });\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(\n                urlLoader.buildLoadData(new GlideUrl(urlLoader.resultUrl), width, height, options))\n            .fetcher);\n  }\n\n  @Test\n  public void testAddsNewUrlToCacheIfNotPresentInCache() {\n    urlLoader.resultUrl = \"fakeUrl\";\n    Object model = new Object();\n    int width = 400;\n    int height = 500;\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                GlideUrl glideUrl = (GlideUrl) invocationOnMock.getArguments()[3];\n                assertEquals(urlLoader.resultUrl, glideUrl.toStringUrl());\n                return null;\n              }\n            })\n        .when(modelCache)\n        .put(eq(model), eq(width), eq(height), any(GlideUrl.class));\n\n    urlLoader.buildLoadData(model, width, height, options);\n\n    verify(modelCache).put(eq(model), eq(width), eq(height), any(GlideUrl.class));\n  }\n\n  @Test\n  public void testDoesNotInteractWithModelCacheIfNull() {\n    TestLoader urlLoader = new TestLoader(wrapped, null);\n    urlLoader.resultUrl = \"fakeUrl\";\n\n    int width = 456;\n    int height = 789;\n\n    when(wrapped.buildLoadData(any(GlideUrl.class), eq(width), eq(height), eq(options)))\n        .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), fetcher));\n\n    assertEquals(\n        fetcher,\n        Preconditions.checkNotNull(urlLoader.buildLoadData(new Object(), width, height, options))\n            .fetcher);\n  }\n\n  private static final class TestLoader extends BaseGlideUrlLoader<Object> {\n    String resultUrl;\n\n    TestLoader(\n        ModelLoader<GlideUrl, InputStream> concreteLoader,\n        ModelCache<Object, GlideUrl> modelCache) {\n      super(concreteLoader, modelCache);\n    }\n\n    @Override\n    protected String getUrl(Object model, int width, int height, Options options) {\n      return resultUrl;\n    }\n\n    @Override\n    public boolean handles(@NonNull Object model) {\n      return true;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/model/stream/HttpGlideUrlLoaderTest.java",
    "content": "package com.bumptech.glide.load.model.stream;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.mock;\n\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.HttpUrlFetcher;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class HttpGlideUrlLoaderTest {\n  private HttpGlideUrlLoader loader;\n  private GlideUrl model;\n\n  @SuppressWarnings(\"unchecked\")\n  @Before\n  public void setUp() {\n    loader = new HttpGlideUrlLoader();\n    model = mock(GlideUrl.class);\n  }\n\n  @Test\n  public void testReturnsValidFetcher() {\n    DataFetcher<InputStream> result =\n        Preconditions.checkNotNull(loader.buildLoadData(model, 100, 100, new Options())).fetcher;\n    assertThat(result).isInstanceOf(HttpUrlFetcher.class);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/SimpleResourceTest.java",
    "content": "package com.bumptech.glide.load.resource;\n\nimport static org.junit.Assert.assertEquals;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class SimpleResourceTest {\n  private Anything object;\n  private SimpleResource<?> resource;\n\n  @Before\n  public void setUp() {\n    object = new Anything();\n    resource = new SimpleResource<>(object);\n  }\n\n  @Test\n  public void testReturnsGivenObject() {\n    assertEquals(object, resource.get());\n  }\n\n  @Test\n  public void testReturnsGivenObjectMultipleTimes() {\n    assertEquals(object, resource.get());\n    assertEquals(object, resource.get());\n    assertEquals(object, resource.get());\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenNullData() {\n    new SimpleResource<>(null);\n  }\n\n  private static class Anything {}\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/UnitTransformationTest.java",
    "content": "package com.bumptech.glide.load.resource;\n\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\n\nimport android.app.Application;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class UnitTransformationTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  private Application app;\n\n  @Before\n  public void setUp() {\n    app = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test\n  public void testReturnsGivenResource() {\n    Resource<Object> resource = mockResource();\n    UnitTransformation<Object> transformation = UnitTransformation.get();\n    assertEquals(resource, transformation.transform(app, resource, 10, 10));\n  }\n\n  @Test\n  public void testEqualsHashCodeDigest() throws NoSuchAlgorithmException {\n    @SuppressWarnings(\"unchecked\")\n    Transformation<Object> other = mock(Transformation.class);\n    doAnswer(new Util.WriteDigest(\"other\"))\n        .when(other)\n        .updateDiskCacheKey(any(MessageDigest.class));\n\n    keyTester\n        .addEquivalenceGroup(UnitTransformation.get(), UnitTransformation.get())\n        .addEquivalenceGroup(other)\n        .addEmptyDigestRegressionTest(UnitTransformation.get())\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableResourceTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotSame;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapDrawableResourceTest {\n  private BitmapDrawableResourceHarness harness;\n\n  @Before\n  public void setUp() {\n    harness = new BitmapDrawableResourceHarness();\n  }\n\n  @Test\n  public void testReturnsGivenBitmapFromGet() {\n    assertEquals(harness.bitmap, harness.create().get().getBitmap());\n  }\n\n  @Test\n  public void testReturnsDifferentDrawableEachTime() {\n    BitmapDrawableResource resource = harness.create();\n    BitmapDrawable first = resource.get();\n    BitmapDrawable second = resource.get();\n\n    assertNotSame(first, second);\n  }\n\n  @Test\n  public void testReturnsSizeFromGivenBitmap() {\n    assertEquals(\n        harness.bitmap.getHeight() * harness.bitmap.getRowBytes(), harness.create().getSize());\n  }\n\n  @Test\n  public void testBitmapIsReturnedToPoolOnRecycle() {\n    harness.create().recycle();\n\n    verify(harness.bitmapPool).put(eq(harness.bitmap));\n  }\n\n  private static class BitmapDrawableResourceHarness {\n    final BitmapPool bitmapPool = mock(BitmapPool.class);\n    final Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    BitmapDrawableResource create() {\n      return new BitmapDrawableResource(\n          new BitmapDrawable(ApplicationProvider.getApplicationContext().getResources(), bitmap),\n          bitmapPool);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableTransformationTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.anyContext;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\n@SuppressWarnings(\"deprecation\")\npublic class BitmapDrawableTransformationTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Mock private BitmapPool bitmapPool;\n  @Mock private Transformation<Bitmap> wrapped;\n  @Mock private Resource<BitmapDrawable> drawableResourceToTransform;\n\n  private BitmapDrawableTransformation transformation;\n  private Bitmap bitmapToTransform;\n  private Application context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    bitmapToTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    BitmapDrawable drawableToTransform = new BitmapDrawable(bitmapToTransform);\n\n    context = ApplicationProvider.getApplicationContext();\n    Glide.init(context, new GlideBuilder().setBitmapPool(bitmapPool));\n    when(drawableResourceToTransform.get()).thenReturn(drawableToTransform);\n    transformation = new BitmapDrawableTransformation(wrapped);\n  }\n\n  @After\n  public void tearDown() {\n    Glide.tearDown();\n  }\n\n  @Test\n  public void testReturnsOriginalResourceIfTransformationDoesNotTransform() {\n    int outWidth = 123;\n    int outHeight = 456;\n    when(wrapped.transform(anyContext(), Util.<Bitmap>anyResource(), eq(outWidth), eq(outHeight)))\n        .thenAnswer(\n            new Answer<Resource<Bitmap>>() {\n              @SuppressWarnings(\"unchecked\")\n              @Override\n              public Resource<Bitmap> answer(InvocationOnMock invocation) throws Throwable {\n                return (Resource<Bitmap>) invocation.getArguments()[1];\n              }\n            });\n\n    Resource<BitmapDrawable> transformed =\n        transformation.transform(context, drawableResourceToTransform, outWidth, outHeight);\n\n    assertThat(transformed).isEqualTo(drawableResourceToTransform);\n  }\n\n  @Test\n  public void testReturnsNewResourceIfTransformationDoesTransform() {\n    int outWidth = 999;\n    int outHeight = 555;\n\n    Bitmap transformedBitmap = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.RGB_565);\n    Resource<Bitmap> transformedBitmapResource = Util.mockResource();\n    when(transformedBitmapResource.get()).thenReturn(transformedBitmap);\n    when(wrapped.transform(anyContext(), Util.<Bitmap>anyResource(), eq(outWidth), eq(outHeight)))\n        .thenReturn(transformedBitmapResource);\n\n    Resource<BitmapDrawable> transformed =\n        transformation.transform(context, drawableResourceToTransform, outWidth, outHeight);\n\n    assertThat(transformed.get().getBitmap()).isEqualTo(transformedBitmap);\n  }\n\n  @Test\n  public void testProvidesBitmapFromGivenResourceToWrappedTransformation() {\n    int outWidth = 332;\n    int outHeight = 111;\n    Resource<Bitmap> transformed = Util.mockResource();\n    when(transformed.get())\n        .thenReturn(Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888));\n    when(wrapped.transform(anyContext(), Util.<Bitmap>anyResource(), anyInt(), anyInt()))\n        .thenReturn(transformed);\n\n    transformation.transform(context, drawableResourceToTransform, outWidth, outHeight);\n    ArgumentCaptor<Resource<Bitmap>> captor = Util.cast(ArgumentCaptor.forClass(Resource.class));\n\n    verify(wrapped).transform(anyContext(), captor.capture(), eq(outWidth), eq(outHeight));\n\n    assertThat(captor.getValue().get()).isEqualTo(bitmapToTransform);\n  }\n\n  @Test\n  public void testEquals() throws NoSuchAlgorithmException {\n    doAnswer(new Util.WriteDigest(\"wrapped\"))\n        .when(wrapped)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    @SuppressWarnings(\"unchecked\")\n    Transformation<Bitmap> other = mock(Transformation.class);\n    doAnswer(new Util.WriteDigest(\"other\"))\n        .when(other)\n        .updateDiskCacheKey(any(MessageDigest.class));\n\n    keyTester\n        .addEquivalenceGroup(transformation, new BitmapDrawableTransformation(wrapped))\n        .addEquivalenceGroup(new BitmapDrawableTransformation(other))\n        .addEquivalenceGroup(wrapped)\n        .addRegressionTest(\n            transformation, \"adbf45b08ad6468aa147e5b2a23758ef56ab631a2b70ad52501ca358441a34f3\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoderTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.CompressFormat;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.EncodeStrategy;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.util.ByteBufferUtil;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapEncoderTest {\n  private EncoderHarness harness;\n\n  @Before\n  public void setUp() {\n    harness = new EncoderHarness();\n  }\n\n  @After\n  public void tearDown() {\n    harness.tearDown();\n  }\n\n  @Test\n  public void testBitmapIsEncoded() throws IOException {\n    harness.bitmap.setHasAlpha(false);\n\n    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));\n  }\n\n  @Test\n  public void testBitmapIsEncodedWithGivenQuality() throws IOException {\n    int quality = 7;\n    harness.setQuality(quality);\n    harness.bitmap.setHasAlpha(false);\n\n    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, quality));\n  }\n\n  @Test\n  public void testEncoderObeysNonNullCompressFormat() throws IOException {\n    Bitmap.CompressFormat format = Bitmap.CompressFormat.WEBP;\n    harness.setFormat(format);\n\n    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.WEBP, 90));\n  }\n\n  @Test\n  public void testEncoderEncodesJpegWithNullFormatAndBitmapWithoutAlpha() throws IOException {\n    harness.setFormat(null);\n    harness.bitmap.setHasAlpha(false);\n\n    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.JPEG, 90));\n  }\n\n  @Test\n  public void testEncoderEncodesPngWithNullFormatAndBitmapWithAlpha() throws IOException {\n    harness.setFormat(null);\n    harness.bitmap.setHasAlpha(true);\n\n    assertThat(harness.encode()).isEqualTo(harness.expectedData(CompressFormat.PNG, 90));\n  }\n\n  @Test\n  public void testReturnsTrueFromWrite() {\n    BitmapEncoder encoder = new BitmapEncoder(harness.arrayPool);\n    assertTrue(encoder.encode(harness.resource, harness.file, harness.options));\n  }\n\n  @Test\n  public void testEncodeStrategy_alwaysReturnsTransformed() {\n    BitmapEncoder encoder = new BitmapEncoder(harness.arrayPool);\n    assertEquals(EncodeStrategy.TRANSFORMED, encoder.getEncodeStrategy(harness.options));\n  }\n\n  private static class EncoderHarness {\n    final Resource<Bitmap> resource = mockResource();\n    final Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    final Options options = new Options();\n    final File file = new File(ApplicationProvider.getApplicationContext().getCacheDir(), \"test\");\n    final ArrayPool arrayPool = new LruArrayPool();\n\n    EncoderHarness() {\n      when(resource.get()).thenReturn(bitmap);\n    }\n\n    void setQuality(int quality) {\n      options.set(BitmapEncoder.COMPRESSION_QUALITY, quality);\n    }\n\n    void setFormat(Bitmap.CompressFormat format) {\n      options.set(BitmapEncoder.COMPRESSION_FORMAT, format);\n    }\n\n    byte[] encode() throws IOException {\n      BitmapEncoder encoder = new BitmapEncoder(arrayPool);\n      encoder.encode(resource, file, options);\n      return ByteBufferUtil.toBytes(ByteBufferUtil.fromFile(file));\n    }\n\n    byte[] expectedData(CompressFormat expectedFormat, int expectedQuality) {\n      ByteArrayOutputStream os = new ByteArrayOutputStream();\n      bitmap.compress(expectedFormat, expectedQuality, os);\n      return os.toByteArray();\n    }\n\n    void tearDown() {\n      // GC before delete() to release files on Windows (https://stackoverflow.com/a/4213208/253468)\n      System.gc();\n      if (file.exists() && !file.delete()) {\n        throw new IllegalStateException(\"Failed to delete: \" + file);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/BitmapResourceTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.tests.Util;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n// TODO: add a test for bitmap size using getAllocationByteSize when robolectric supports kitkat.\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapResourceTest {\n  private int currentBuildVersion;\n  private BitmapResourceHarness harness;\n\n  @Before\n  public void setUp() {\n    currentBuildVersion = Build.VERSION.SDK_INT;\n    harness = new BitmapResourceHarness();\n  }\n\n  @After\n  public void tearDown() {\n    Util.setSdkVersionInt(currentBuildVersion);\n  }\n\n  @Test\n  public void testCanGetBitmap() {\n    assertEquals(harness.bitmap, harness.resource.get());\n  }\n\n  @Test\n  public void testSizeIsBasedOnDimensPreKitKat() {\n    Util.setSdkVersionInt(18);\n    assertEquals(\n        harness.bitmap.getWidth() * harness.bitmap.getHeight() * 4, harness.resource.getSize());\n  }\n\n  @Test\n  public void testPutsBitmapInPoolOnRecycle() {\n    harness.resource.recycle();\n\n    verify(harness.bitmapPool).put(eq(harness.bitmap));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfBitmapIsNull() {\n    new BitmapResource(null, mock(BitmapPool.class));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfBitmapPoolIsNull() {\n    new BitmapResource(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565), null);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfBitmapAndBitmapPoolAreNull() {\n    new BitmapResource(null, null);\n  }\n\n  private static class BitmapResourceHarness {\n    final Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    final BitmapPool bitmapPool = mock(BitmapPool.class);\n    final BitmapResource resource = new BitmapResource(bitmap, bitmapPool);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/BitmapTransformationTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotSame;\nimport static org.junit.Assert.assertNull;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapTransformationTest {\n\n  @Mock private BitmapPool bitmapPool;\n  private Application context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n\n    Glide.init(context, new GlideBuilder().setBitmapPool(bitmapPool));\n  }\n\n  @After\n  public void tearDown() {\n    Glide.tearDown();\n  }\n\n  @Test\n  public void testReturnsGivenResourceWhenBitmapNotTransformed() {\n    BitmapTransformation transformation =\n        new BitmapTransformation() {\n          @Override\n          public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {}\n\n          @Override\n          protected Bitmap transform(\n              @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n            return toTransform;\n          }\n        };\n\n    Resource<Bitmap> resource = mockResource(100, 100);\n    assertEquals(resource, transformation.transform(context, resource, 1, 1));\n  }\n\n  @Test\n  public void testReturnsNewResourceWhenBitmapTransformed() {\n    final Bitmap transformed = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_4444);\n    BitmapTransformation transformation =\n        new BitmapTransformation() {\n          @Override\n          public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {}\n\n          @Override\n          protected Bitmap transform(\n              @NonNull BitmapPool pool, @NonNull Bitmap bitmap, int outWidth, int outHeight) {\n            return transformed;\n          }\n        };\n\n    Resource<Bitmap> resource = mockResource(1, 2);\n    assertNotSame(resource, transformation.transform(context, resource, 100, 100));\n  }\n\n  @Test\n  public void testPassesGivenArgumentsToTransform() {\n    final int expectedWidth = 13;\n    final int expectedHeight = 148;\n    final Resource<Bitmap> resource = mockResource(223, 4123);\n    BitmapTransformation transformation =\n        new BitmapTransformation() {\n          @Override\n          public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {}\n\n          @Override\n          protected Bitmap transform(\n              @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n            assertEquals(bitmapPool, pool);\n            assertEquals(resource.get(), toTransform);\n            assertEquals(expectedWidth, outWidth);\n            assertEquals(expectedHeight, outHeight);\n            return resource.get();\n          }\n        };\n\n    transformation.transform(context, resource, expectedWidth, expectedHeight);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfGivenInvalidWidth() {\n    BitmapTransformation transformation =\n        new BitmapTransformation() {\n\n          @Override\n          public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {}\n\n          @Override\n          protected Bitmap transform(\n              @NonNull BitmapPool bitmapPool,\n              @NonNull Bitmap toTransform,\n              int outWidth,\n              int outHeight) {\n            return null;\n          }\n        };\n    transformation.transform(context, mockResource(1, 1), -1, 100);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfGivenInvalidHeight() {\n    BitmapTransformation transformation =\n        new BitmapTransformation() {\n\n          @Override\n          public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {}\n\n          @Override\n          protected Bitmap transform(\n              @NonNull BitmapPool bitmapPool,\n              @NonNull Bitmap toTransform,\n              int outWidth,\n              int outHeight) {\n            return null;\n          }\n        };\n    transformation.transform(context, mockResource(1, 1), 100, -1);\n  }\n\n  @Test\n  public void testReturnsNullIfTransformReturnsNull() {\n    BitmapTransformation transform =\n        new BitmapTransformation() {\n\n          @Override\n          public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {}\n\n          @Override\n          protected Bitmap transform(\n              @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n            return null;\n          }\n        };\n\n    Resource<Bitmap> resource = mockResource(100, 100);\n    assertNull(transform.transform(context, resource, 100, 100));\n  }\n\n  @Test\n  public void testCallsTransformWithGivenBitmapWidthIfWidthIsSizeOriginal() {\n    SizeTrackingTransform transform = new SizeTrackingTransform();\n\n    int expectedWidth = 200;\n    Resource<Bitmap> resource = mockResource(expectedWidth, 300);\n    transform.transform(context, resource, Target.SIZE_ORIGINAL, 500);\n\n    assertEquals(expectedWidth, transform.givenWidth);\n  }\n\n  @Test\n  public void testCallsTransformWithGivenBitmapHeightIfHeightIsSizeOriginal() {\n    SizeTrackingTransform transform = new SizeTrackingTransform();\n\n    int expectedHeight = 500;\n    Resource<Bitmap> resource = mockResource(123, expectedHeight);\n    transform.transform(context, resource, 444, expectedHeight);\n\n    assertEquals(expectedHeight, transform.givenHeight);\n  }\n\n  private Resource<Bitmap> mockResource(int width, int height) {\n    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n    Resource<Bitmap> resource = Util.mockResource();\n    when(resource.get()).thenReturn(bitmap);\n    return resource;\n  }\n\n  private static final class SizeTrackingTransform extends BitmapTransformation {\n    int givenWidth;\n    int givenHeight;\n\n    @Override\n    protected Bitmap transform(\n        @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {\n      givenWidth = outWidth;\n      givenHeight = outHeight;\n      return null;\n    }\n\n    @Override\n    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {}\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/CenterCropTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isNull;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = 28)\npublic class CenterCropTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n  @Mock private Resource<Bitmap> resource;\n  @Mock private BitmapPool pool;\n  @Mock private Transformation<Bitmap> transformation;\n\n  private CenterCrop centerCrop;\n  private int bitmapWidth;\n  private int bitmapHeight;\n  private Bitmap bitmap;\n  private Application context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    bitmapWidth = 100;\n    bitmapHeight = 100;\n    bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);\n    when(resource.get()).thenReturn(bitmap);\n\n    when(pool.get(anyInt(), anyInt(), any(Bitmap.Config.class)))\n        .thenAnswer(new Util.CreateBitmap());\n    context = ApplicationProvider.getApplicationContext();\n    Glide.init(context, new GlideBuilder().setBitmapPool(pool));\n\n    centerCrop = new CenterCrop();\n  }\n\n  @After\n  public void tearDown() {\n    Glide.tearDown();\n  }\n\n  @Test\n  public void testDoesNotPutNullBitmapAcquiredFromPool() {\n    reset(pool);\n    when(pool.get(anyInt(), anyInt(), any(Bitmap.Config.class))).thenReturn(null);\n\n    centerCrop.transform(context, resource, 100, 100);\n\n    verify(pool, never()).put(any(Bitmap.class));\n  }\n\n  @Test\n  public void testReturnsGivenResourceIfMatchesSizeExactly() {\n    Resource<Bitmap> result = centerCrop.transform(context, resource, bitmapWidth, bitmapHeight);\n\n    assertEquals(resource, result);\n  }\n\n  @Test\n  public void testDoesNotRecycleGivenResourceIfMatchesSizeExactly() {\n    centerCrop.transform(context, resource, bitmapWidth, bitmapHeight);\n\n    verify(resource, never()).recycle();\n  }\n\n  @Test\n  public void testDoesNotRecycleGivenResource() {\n    centerCrop.transform(context, resource, 50, 50);\n\n    verify(resource, never()).recycle();\n  }\n\n  @Test\n  @Config(sdk = 19)\n  public void testAsksBitmapPoolForArgb8888IfInConfigIsNull() {\n    bitmap.setConfig(null);\n\n    centerCrop.transform(context, resource, 10, 10);\n\n    verify(pool).get(anyInt(), anyInt(), eq(Bitmap.Config.ARGB_8888));\n    verify(pool, never()).get(anyInt(), anyInt(), (Bitmap.Config) isNull());\n  }\n\n  @Test\n  public void testReturnsBitmapWithExactlyGivenDimensionsIfBitmapIsLargerThanTarget() {\n    int expectedWidth = 75;\n    int expectedHeight = 74;\n\n    for (int[] dimens :\n        new int[][] {new int[] {800, 200}, new int[] {450, 100}, new int[] {78, 78}}) {\n      Bitmap toTransform = Bitmap.createBitmap(dimens[0], dimens[1], Bitmap.Config.ARGB_4444);\n      when(resource.get()).thenReturn(toTransform);\n\n      Resource<Bitmap> result =\n          centerCrop.transform(context, resource, expectedWidth, expectedHeight);\n      Bitmap transformed = result.get();\n      assertEquals(expectedWidth, transformed.getWidth());\n      assertEquals(expectedHeight, transformed.getHeight());\n    }\n  }\n\n  @Test\n  public void testReturnsBitmapWithExactlyGivenDimensionsIfBitmapIsSmallerThanTarget() {\n    int expectedWidth = 100;\n    int expectedHeight = 100;\n\n    for (int[] dimens : new int[][] {new int[] {50, 90}, new int[] {150, 2}, new int[] {78, 78}}) {\n      Bitmap toTransform = Bitmap.createBitmap(dimens[0], dimens[1], Bitmap.Config.ARGB_4444);\n      when(resource.get()).thenReturn(toTransform);\n\n      Resource<Bitmap> result =\n          centerCrop.transform(context, resource, expectedWidth, expectedHeight);\n      Bitmap transformed = result.get();\n      assertEquals(expectedWidth, transformed.getWidth());\n      assertEquals(expectedHeight, transformed.getHeight());\n    }\n  }\n\n  @Test\n  public void testEquals() throws NoSuchAlgorithmException {\n    doAnswer(new Util.WriteDigest(\"other\"))\n        .when(transformation)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    keyTester\n        .addEquivalenceGroup(new CenterCrop(), new CenterCrop())\n        .addEquivalenceGroup(transformation)\n        .addRegressionTest(\n            new CenterCrop(), \"68bd5819c42b37efbe7124bb851443a6388ee3e2e9034213da6eaa15381d3457\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/CenterInsideTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class CenterInsideTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Mock private Resource<Bitmap> resource;\n  @Mock private Transformation<Bitmap> transformation;\n  private CenterInside centerInside;\n  private int bitmapWidth;\n  private int bitmapHeight;\n  private Application context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    bitmapWidth = 100;\n    bitmapHeight = 100;\n    Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);\n    when(resource.get()).thenReturn(bitmap);\n\n    context = ApplicationProvider.getApplicationContext();\n    BitmapPool pool = new BitmapPoolAdapter();\n    Glide.init(context, new GlideBuilder().setBitmapPool(pool));\n\n    centerInside = new CenterInside();\n  }\n\n  @After\n  public void tearDown() {\n    Glide.tearDown();\n  }\n\n  @Test\n  public void testReturnsGivenResourceIfMatchesSizeExactly() {\n    Resource<Bitmap> result = centerInside.transform(context, resource, bitmapWidth, bitmapHeight);\n\n    assertEquals(resource, result);\n  }\n\n  @Test\n  public void testReturnsGivenResourceIfSmallerThanTarget() {\n    Resource<Bitmap> result = centerInside.transform(context, resource, 150, 150);\n\n    assertEquals(resource, result);\n  }\n\n  @Test\n  public void testReturnsNewResourceIfLargerThanTarget() {\n    Resource<Bitmap> result = centerInside.transform(context, resource, 50, 50);\n\n    assertNotEquals(resource, result);\n  }\n\n  @Test\n  public void testDoesNotRecycleGivenResourceIfMatchesSizeExactly() {\n    centerInside.transform(context, resource, bitmapWidth, bitmapHeight);\n\n    verify(resource, never()).recycle();\n  }\n\n  @Test\n  public void testDoesNotRecycleGivenResource() {\n    centerInside.transform(context, resource, 50, 50);\n\n    verify(resource, never()).recycle();\n  }\n\n  @Test\n  public void testEquals() throws NoSuchAlgorithmException {\n    doAnswer(new Util.WriteDigest(\"other\"))\n        .when(transformation)\n        .updateDiskCacheKey(any(MessageDigest.class));\n\n    keyTester\n        .addEquivalenceGroup(new CenterInside(), new CenterInside(), centerInside)\n        .addEquivalenceGroup(transformation)\n        .addRegressionTest(\n            new CenterInside(), \"acf83850a2e8e9e809c8bfb999e2aede9e932cb897a15367fac9856b96f3ba33\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/CircleCropTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class CircleCropTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n  @Mock private BitmapPool bitmapPool;\n\n  private CircleCrop circleCrop;\n\n  @Before\n  public void setup() {\n    MockitoAnnotations.initMocks(this);\n    when(bitmapPool.get(anyInt(), anyInt(), any(Bitmap.Config.class)))\n        .thenAnswer(new Util.CreateBitmap());\n    Context context = ApplicationProvider.getApplicationContext();\n    Glide.init(context, new GlideBuilder().setBitmapPool(bitmapPool));\n    circleCrop = new CircleCrop();\n  }\n\n  @After\n  public void tearDown() {\n    Glide.tearDown();\n  }\n\n  @Test\n  public void testTransform_withSquare() {\n    Bitmap redSquare = createSolidRedBitmap(50, 50);\n    Bitmap result = circleCrop.transform(bitmapPool, redSquare, 50, 50);\n    Bitmap expected = createBitmapWithRedCircle(50, 50);\n\n    assertSamePixels(expected, result);\n  }\n\n  @Test\n  public void testTransform_reusesBitmap() {\n    Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);\n    when(bitmapPool.get(50, 50, Bitmap.Config.ARGB_8888)).thenReturn(toReuse);\n\n    Bitmap redSquare = createSolidRedBitmap(50, 50);\n    Bitmap result = circleCrop.transform(bitmapPool, redSquare, 50, 50);\n\n    assertEquals(toReuse, result);\n  }\n\n  @Test\n  public void testTransform_withWideRectangle() {\n    Bitmap redWideRectangle = createSolidRedBitmap(100, 50);\n    Bitmap result = circleCrop.transform(bitmapPool, redWideRectangle, 80, 50);\n    Bitmap expected = createBitmapWithRedCircle(80, 50);\n\n    assertSamePixels(expected, result);\n  }\n\n  @Test\n  public void testTransform_withNarrowRectangle() {\n    Bitmap redNarrowRectangle = createSolidRedBitmap(20, 50);\n    Bitmap result = circleCrop.transform(bitmapPool, redNarrowRectangle, 40, 80);\n    Bitmap expected = createBitmapWithRedCircle(40, 80);\n\n    assertSamePixels(expected, result);\n  }\n\n  @Test\n  public void testEquals() {\n    keyTester\n        .addEquivalenceGroup(circleCrop, new CircleCrop())\n        .addEquivalenceGroup(mock(Transformation.class))\n        .addRegressionTest(\n            new CircleCrop(), \"1442365bcc658f89310e39844ef4be58f4b16e52c283254e5a458020f56acb90\")\n        .test();\n  }\n\n  private void assertSamePixels(Bitmap expected, Bitmap actual) {\n    assertEquals(expected.getWidth(), actual.getWidth());\n    assertEquals(expected.getHeight(), actual.getHeight());\n    assertEquals(expected.getConfig(), actual.getConfig());\n    for (int y = 0; y < expected.getHeight(); y++) {\n      for (int x = 0; x < expected.getWidth(); x++) {\n        assertEquals(expected.getPixel(x, y), actual.getPixel(x, y));\n      }\n    }\n  }\n\n  private Bitmap createBitmapWithRedCircle(int width, int height) {\n    int minEdge = Math.min(width, height);\n    float radius = minEdge / 2f;\n\n    Bitmap result = Bitmap.createBitmap(minEdge, minEdge, Bitmap.Config.ARGB_8888);\n    result.setHasAlpha(true);\n    Canvas canvas = new Canvas(result);\n    Paint paint = new Paint();\n    paint.setAntiAlias(true);\n    paint.setColor(Color.RED);\n\n    canvas.drawCircle(radius, radius, radius, paint);\n    return result;\n  }\n\n  private Bitmap createSolidRedBitmap(int width, int height) {\n    Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n    Canvas canvas = new Canvas(result);\n    Paint paint = new Paint();\n    paint.setColor(Color.RED);\n    Rect rect = new Rect(0, 0, width, height);\n    canvas.drawRect(rect, paint);\n    return result;\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/DefaultImageHeaderParserTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotEquals;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser.ImageType;\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.testutil.TestResourceUtil;\nimport com.google.common.io.ByteStreams;\nimport java.io.ByteArrayInputStream;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class DefaultImageHeaderParserTest {\n\n  private static final byte[] PNG_HEADER_WITH_IHDR_CHUNK =\n      new byte[] {\n        (byte) 0x89,\n        0x50,\n        0x4e,\n        0x47,\n        0xd,\n        0xa,\n        0x1a,\n        0xa,\n        0x0,\n        0x0,\n        0x0,\n        0xd,\n        0x49,\n        0x48,\n        0x44,\n        0x52,\n        0x0,\n        0x0,\n        0x1,\n        (byte) 0x90,\n        0x0,\n        0x0,\n        0x1,\n        0x2c,\n        0x8,\n        0x6\n      };\n\n  private ArrayPool byteArrayPool;\n\n  @Before\n  public void setUp() {\n    byteArrayPool = new LruArrayPool();\n  }\n\n  @Test\n  public void testCanParsePngType() throws IOException {\n    // PNG magic number from: http://en.wikipedia.org/wiki/Portable_Network_Graphics.\n    byte[] data = new byte[] {(byte) 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.PNG, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.PNG, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParsePngWithAlpha() throws IOException {\n    for (int i = 3; i <= 6; i++) {\n      byte[] pngHeaderWithIhdrChunk = generatePngHeaderWithIhdr(i);\n      runTest(\n          pngHeaderWithIhdrChunk,\n          new ParserTestCase() {\n            @Override\n            public void run(\n                DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n                throws IOException {\n              assertEquals(ImageType.PNG_A, parser.getType(is));\n            }\n\n            @Override\n            public void run(\n                DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n                throws IOException {\n              assertEquals(ImageType.PNG_A, parser.getType(byteBuffer));\n            }\n          });\n    }\n  }\n\n  @Test\n  public void testCanParsePngWithoutAlpha() throws IOException {\n    for (int i = 0; i < 3; i++) {\n      byte[] pngHeaderWithIhdrChunk = generatePngHeaderWithIhdr(i);\n      runTest(\n          pngHeaderWithIhdrChunk,\n          new ParserTestCase() {\n            @Override\n            public void run(\n                DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n                throws IOException {\n              assertEquals(ImageType.PNG, parser.getType(is));\n            }\n\n            @Override\n            public void run(\n                DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n                throws IOException {\n              assertEquals(ImageType.PNG, parser.getType(byteBuffer));\n            }\n          });\n    }\n  }\n\n  @Test\n  public void testCanParseJpegType() throws IOException {\n    byte[] data = new byte[] {(byte) 0xFF, (byte) 0xD8};\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.JPEG, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.JPEG, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseGifType() throws IOException {\n    byte[] data = new byte[] {'G', 'I', 'F'};\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.GIF, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.GIF, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseLosslessWebpWithAlpha() throws IOException {\n    byte[] data =\n        new byte[] {\n          0x52,\n          0x49,\n          0x46,\n          0x46,\n          0x3c,\n          0x50,\n          0x00,\n          0x00,\n          0x57,\n          0x45,\n          0x42,\n          0x50,\n          0x56,\n          0x50,\n          0x38,\n          0x4c, // Lossless\n          0x30,\n          0x50,\n          0x00,\n          0x00,\n          0x2f, // Flags\n          (byte) 0xef,\n          (byte) 0x80,\n          0x15,\n          0x10,\n          (byte) 0x8d,\n          0x30,\n          0x68,\n          0x1b,\n          (byte) 0xc9,\n          (byte) 0x91,\n          (byte) 0xb2\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.WEBP_A, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.WEBP_A, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseLosslessWebpWithoutAlpha() throws IOException {\n    byte[] data =\n        new byte[] {\n          0x52,\n          0x49,\n          0x46,\n          0x46,\n          0x3c,\n          0x50,\n          0x00,\n          0x00,\n          0x57,\n          0x45,\n          0x42,\n          0x50,\n          0x56,\n          0x50,\n          0x38,\n          0x4c, // Lossless\n          0x30,\n          0x50,\n          0x00,\n          0x00,\n          0x00, // Flags\n          (byte) 0xef,\n          (byte) 0x80,\n          0x15,\n          0x10,\n          (byte) 0x8d,\n          0x30,\n          0x68,\n          0x1b,\n          (byte) 0xc9,\n          (byte) 0x91,\n          (byte) 0xb2\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.WEBP, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.WEBP, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseExtendedWebpWithAlpha() throws IOException {\n    byte[] data =\n        new byte[] {\n          0x52,\n          0x49,\n          0x46,\n          0x46,\n          0x3c,\n          0x50,\n          0x00,\n          0x00,\n          0x57,\n          0x45,\n          0x42,\n          0x50,\n          0x56,\n          0x50,\n          0x38,\n          0x58, // Extended\n          0x30,\n          0x50,\n          0x00,\n          0x00,\n          0x10, // flags\n          (byte) 0xef,\n          (byte) 0x80,\n          0x15,\n          0x10,\n          (byte) 0x8d,\n          0x30,\n          0x68,\n          0x1b,\n          (byte) 0xc9,\n          (byte) 0x91,\n          (byte) 0xb2\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.WEBP_A, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.WEBP_A, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseExtendedWebpWithoutAlpha() throws IOException {\n    byte[] data =\n        new byte[] {\n          0x52,\n          0x49,\n          0x46,\n          0x46,\n          0x3c,\n          0x50,\n          0x00,\n          0x00,\n          0x57,\n          0x45,\n          0x42,\n          0x50,\n          0x56,\n          0x50,\n          0x38,\n          0x58, // Extended\n          0x30,\n          0x50,\n          0x00,\n          0x00,\n          0x00, // flags\n          (byte) 0xef,\n          (byte) 0x80,\n          0x15,\n          0x10,\n          (byte) 0x8d,\n          0x30,\n          0x68,\n          0x1b,\n          (byte) 0xc9,\n          (byte) 0x91,\n          (byte) 0xb2\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.WEBP, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.WEBP, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseExtendedWebpWithoutAlphaAndWithAnimation() throws IOException {\n    byte[] data =\n        new byte[] {\n          0x52,\n          0x49,\n          0x46,\n          0x46,\n          0x3c,\n          0x50,\n          0x00,\n          0x00,\n          0x57,\n          0x45,\n          0x42,\n          0x50,\n          0x56,\n          0x50,\n          0x38,\n          0x58, // Extended\n          0x30,\n          0x50,\n          0x00,\n          0x00,\n          0x02, // Flags\n          (byte) 0xef,\n          (byte) 0x80,\n          0x15,\n          0x10,\n          (byte) 0x8d,\n          0x30,\n          0x68,\n          0x1b,\n          (byte) 0xc9,\n          (byte) 0x91,\n          (byte) 0xb2\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_WEBP, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_WEBP, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseExtendedWebpWithAlphaAndAnimation() throws IOException {\n    byte[] data =\n        new byte[] {\n          0x52,\n          0x49,\n          0x46,\n          0x46,\n          0x3c,\n          0x50,\n          0x00,\n          0x00,\n          0x57,\n          0x45,\n          0x42,\n          0x50,\n          0x56,\n          0x50,\n          0x38,\n          0x58, // Extended\n          0x30,\n          0x50,\n          0x00,\n          0x00,\n          (byte) 0x12, // Flags\n          (byte) 0xef,\n          (byte) 0x80,\n          0x15,\n          0x10,\n          (byte) 0x8d,\n          0x30,\n          0x68,\n          0x1b,\n          (byte) 0xc9,\n          (byte) 0x91,\n          (byte) 0xb2\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_WEBP, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_WEBP, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseRealAnimatedWebpFile() throws IOException {\n    byte[] data =\n        ByteStreams.toByteArray(TestResourceUtil.openResource(getClass(), \"animated_webp.webp\"));\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertThat(parser.getType(is)).isEqualTo(ImageType.ANIMATED_WEBP);\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertThat(parser.getType(byteBuffer)).isEqualTo(ImageType.ANIMATED_WEBP);\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseAvifMajorBrand() throws IOException {\n    byte[] data =\n        new byte[] {\n          // Box Size.\n          0x00,\n          0x00,\n          0x00,\n          0x1C,\n          // ftyp.\n          0x66,\n          0x74,\n          0x79,\n          0x70,\n          // avif (major brand).\n          0x61,\n          0x76,\n          0x69,\n          0x66,\n          // minor version.\n          0x00,\n          0x00,\n          0x00,\n          0x00,\n          // other minor brands (mif1, miaf, MA1B).\n          0x6d,\n          0x69,\n          0x66,\n          0x31,\n          0x6d,\n          0x69,\n          0x61,\n          0x66,\n          0x4d,\n          0x41,\n          0x31,\n          0x42\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.AVIF, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.AVIF, parser.getType(byteBuffer));\n          }\n        });\n    // Change the major brand from 'avif' to 'avis'. Now, the expected output is ANIMATED_AVIF.\n    data[11] = 0x73;\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_AVIF, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_AVIF, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseAvifMinorBrand() throws IOException {\n    byte[] data =\n        new byte[] {\n          // Box Size.\n          0x00,\n          0x00,\n          0x00,\n          0x1C,\n          // ftyp.\n          0x66,\n          0x74,\n          0x79,\n          0x70,\n          // mif1 (major brand).\n          0x6d,\n          0x69,\n          0x66,\n          0x31,\n          // minor version.\n          0x00,\n          0x00,\n          0x00,\n          0x00,\n          // other minor brands (miaf, avif, MA1B).\n          0x6d,\n          0x69,\n          0x61,\n          0x66,\n          0x61,\n          0x76,\n          0x69,\n          0x66,\n          0x4d,\n          0x41,\n          0x31,\n          0x42\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.AVIF, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.AVIF, parser.getType(byteBuffer));\n          }\n        });\n    // Change the last minor brand from 'MA1B' to 'avis'. Now, the expected output is ANIMATED_AVIF.\n    data[24] = 0x61;\n    data[25] = 0x76;\n    data[26] = 0x69;\n    data[27] = 0x73;\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_AVIF, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_AVIF, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseAvifAndAvisBrandsAsAnimatedAvif() throws IOException {\n    byte[] data =\n        new byte[] {\n          // Box Size.\n          0x00,\n          0x00,\n          0x00,\n          0x1C,\n          // ftyp.\n          0x66,\n          0x74,\n          0x79,\n          0x70,\n          // avis (major brand).\n          0x61,\n          0x76,\n          0x69,\n          0x73,\n          // minor version.\n          0x00,\n          0x00,\n          0x00,\n          0x00,\n          // other minor brands (miaf, avif, MA1B).\n          0x6d,\n          0x69,\n          0x61,\n          0x66,\n          0x61,\n          0x76,\n          0x69,\n          0x66,\n          0x4d,\n          0x41,\n          0x31,\n          0x42\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_AVIF, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_AVIF, parser.getType(byteBuffer));\n          }\n        });\n    // Change the major brand from 'avis' to 'avif'.\n    data[11] = 0x66;\n    // Change the minor brand from 'avif' to 'avis'.\n    data[23] = 0x73;\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_AVIF, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.ANIMATED_AVIF, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCannotParseAvifMoreThanFiveMinorBrands() throws IOException {\n    byte[] data =\n        new byte[] {\n          // Box Size.\n          0x00,\n          0x00,\n          0x00,\n          0x28,\n          // ftyp.\n          0x66,\n          0x74,\n          0x79,\n          0x70,\n          // mif1 (major brand).\n          0x6d,\n          0x69,\n          0x66,\n          0x31,\n          // minor version.\n          0x00,\n          0x00,\n          0x00,\n          0x00,\n          // more than five minor brands with the sixth one being avif (mif1, miaf, MA1B, mif1,\n          // miab, avif).\n          0x6d,\n          0x69,\n          0x66,\n          0x31,\n          0x6d,\n          0x69,\n          0x61,\n          0x66,\n          0x4d,\n          0x41,\n          0x31,\n          0x42,\n          0x6d,\n          0x69,\n          0x66,\n          0x31,\n          0x6d,\n          0x69,\n          0x61,\n          0x66,\n          0x61,\n          0x76,\n          0x69,\n          0x66,\n        };\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertNotEquals(ImageType.AVIF, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertNotEquals(ImageType.AVIF, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  @Test\n  public void testCanParseRealAnimatedAvifFile() throws IOException {\n    byte[] data =\n        ByteStreams.toByteArray(TestResourceUtil.openResource(getClass(), \"animated_avif.avif\"));\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertThat(parser.getType(is)).isEqualTo(ImageType.ANIMATED_AVIF);\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertThat(parser.getType(byteBuffer)).isEqualTo(ImageType.ANIMATED_AVIF);\n          }\n        });\n  }\n\n  @Test\n  public void testReturnsUnknownTypeForUnknownImageHeaders() throws IOException {\n    byte[] data = new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.UNKNOWN, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.UNKNOWN, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  // Test for #286.\n  @Test\n  public void testHandlesParsingOrientationWithMinimalExifSegment() throws IOException {\n    byte[] data =\n        ByteStreams.toByteArray(TestResourceUtil.openResource(getClass(), \"short_exif_sample.jpg\"));\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(-1, parser.getOrientation(is, byteArrayPool));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(-1, parser.getOrientation(byteBuffer, byteArrayPool));\n          }\n        });\n  }\n\n  @Test\n  public void testReturnsUnknownForEmptyData() throws IOException {\n    runTest(\n        new byte[0],\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.UNKNOWN, parser.getType(is));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(ImageType.UNKNOWN, parser.getType(byteBuffer));\n          }\n        });\n  }\n\n  // Test for #387.\n  @Test\n  public void testHandlesPartialReads() throws IOException {\n    InputStream is = TestResourceUtil.openResource(getClass(), \"issue387_rotated_jpeg.jpg\");\n    DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n    assertThat(parser.getOrientation(new PartialReadInputStream(is), byteArrayPool)).isEqualTo(6);\n  }\n\n  // Test for #387.\n  @Test\n  public void testHandlesPartialSkips() throws IOException {\n    InputStream is = TestResourceUtil.openResource(getClass(), \"issue387_rotated_jpeg.jpg\");\n    DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n    assertThat(parser.getOrientation(new PartialSkipInputStream(is), byteArrayPool)).isEqualTo(6);\n  }\n\n  @Test\n  public void testHandlesSometimesZeroSkips() throws IOException {\n    InputStream is =\n        new ByteArrayInputStream(\n            new byte[] {(byte) 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a});\n    DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n    assertEquals(ImageType.PNG, parser.getType(new SometimesZeroSkipInputStream(is)));\n  }\n\n  @Test\n  public void getOrientation_withExifSegmentLessThanLength_returnsUnknown() throws IOException {\n    ByteBuffer jpegHeaderBytes = getExifMagicNumber();\n    byte[] data =\n        new byte[] {\n          jpegHeaderBytes.get(0),\n          jpegHeaderBytes.get(1),\n          (byte) DefaultImageHeaderParser.SEGMENT_START_ID,\n          (byte) DefaultImageHeaderParser.EXIF_SEGMENT_TYPE,\n          // SEGMENT_LENGTH\n          (byte) 0xFF,\n          (byte) 0xFF,\n        };\n    ByteBuffer byteBuffer = ByteBuffer.wrap(data);\n    DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n    assertEquals(\n        ImageHeaderParser.UNKNOWN_ORIENTATION, parser.getOrientation(byteBuffer, byteArrayPool));\n  }\n\n  @Test\n  public void getOrientation_withNonExifSegmentLessThanLength_returnsUnknown() throws IOException {\n    ByteBuffer jpegHeaderBytes = getExifMagicNumber();\n    byte[] data =\n        new byte[] {\n          jpegHeaderBytes.get(0),\n          jpegHeaderBytes.get(1),\n          (byte) DefaultImageHeaderParser.SEGMENT_START_ID,\n          // SEGMENT_TYPE (NOT EXIF_SEGMENT_TYPE)\n          (byte) 0xE5,\n          // SEGMENT_LENGTH\n          (byte) 0xFF,\n          (byte) 0xFF,\n        };\n    ByteBuffer byteBuffer = ByteBuffer.wrap(data);\n    DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n    assertEquals(\n        ImageHeaderParser.UNKNOWN_ORIENTATION, parser.getOrientation(byteBuffer, byteArrayPool));\n  }\n\n  @Test\n  public void getOrientation_withExifSegmentAndPreambleButLessThanLength_returnsUnknown()\n      throws IOException {\n    ByteBuffer jpegHeaderBytes = getExifMagicNumber();\n    ByteBuffer exifSegmentPreamble =\n        ByteBuffer.wrap(DefaultImageHeaderParser.JPEG_EXIF_SEGMENT_PREAMBLE_BYTES);\n\n    ByteBuffer data = ByteBuffer.allocate(2 + 1 + 1 + 2 + exifSegmentPreamble.capacity());\n    data.put(jpegHeaderBytes)\n        .put((byte) DefaultImageHeaderParser.SEGMENT_START_ID)\n        .put((byte) DefaultImageHeaderParser.EXIF_SEGMENT_TYPE)\n        // SEGMENT_LENGTH, add two because length includes the segment length short, and one to go\n        // beyond the preamble bytes length for the test.\n        .putShort(\n            (short) (DefaultImageHeaderParser.JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length + 2 + 1))\n        .put(DefaultImageHeaderParser.JPEG_EXIF_SEGMENT_PREAMBLE_BYTES);\n\n    data.position(0);\n    DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n    assertEquals(ImageHeaderParser.UNKNOWN_ORIENTATION, parser.getOrientation(data, byteArrayPool));\n  }\n\n  @Test\n  public void getOrientation_withExifSegmentAndPreambleBetweenLengthAndExpected_returnsUnknown()\n      throws IOException {\n    ByteBuffer jpegHeaderBytes = getExifMagicNumber();\n    ByteBuffer exifSegmentPreamble =\n        ByteBuffer.wrap(DefaultImageHeaderParser.JPEG_EXIF_SEGMENT_PREAMBLE_BYTES);\n\n    ByteBuffer data = ByteBuffer.allocate(2 + 1 + 1 + 2 + exifSegmentPreamble.capacity() + 2 + 1);\n    data.put(jpegHeaderBytes)\n        .put((byte) DefaultImageHeaderParser.SEGMENT_START_ID)\n        .put((byte) DefaultImageHeaderParser.EXIF_SEGMENT_TYPE)\n        // SEGMENT_LENGTH, add two because length includes the segment length short, and one to go\n        // beyond the preamble bytes length for the test.\n        .putShort(\n            (short) (DefaultImageHeaderParser.JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length + 2 + 1))\n        .put(DefaultImageHeaderParser.JPEG_EXIF_SEGMENT_PREAMBLE_BYTES);\n\n    data.position(0);\n    DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n    assertEquals(ImageHeaderParser.UNKNOWN_ORIENTATION, parser.getOrientation(data, byteArrayPool));\n  }\n\n  @Test\n  public void hasJpegMpf_withGainmapFile_returnsTrue() throws IOException {\n    byte[] data =\n        ByteStreams.toByteArray(\n            TestResourceUtil.openResource(getClass(), \"small_gainmap_image.jpg\"));\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(true, parser.hasJpegMpf(is, byteArrayPool));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(true, parser.hasJpegMpf(byteBuffer, byteArrayPool));\n          }\n        });\n  }\n\n  @Test\n  public void hasJpegMpf_withNonGainmapFile_returnsFalse() throws IOException {\n    byte[] data =\n        ByteStreams.toByteArray(TestResourceUtil.openResource(getClass(), \"short_exif_sample.jpg\"));\n    runTest(\n        data,\n        new ParserTestCase() {\n          @Override\n          public void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(false, parser.hasJpegMpf(is, byteArrayPool));\n          }\n\n          @Override\n          public void run(\n              DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n              throws IOException {\n            assertEquals(false, parser.hasJpegMpf(byteBuffer, byteArrayPool));\n          }\n        });\n  }\n\n  private static ByteBuffer getExifMagicNumber() {\n    ByteBuffer jpegHeaderBytes = ByteBuffer.allocate(2);\n    jpegHeaderBytes.putShort((short) DefaultImageHeaderParser.EXIF_MAGIC_NUMBER);\n    jpegHeaderBytes.position(0);\n    return jpegHeaderBytes;\n  }\n\n  private interface ParserTestCase {\n    void run(DefaultImageHeaderParser parser, InputStream is, ArrayPool byteArrayPool)\n        throws IOException;\n\n    void run(DefaultImageHeaderParser parser, ByteBuffer byteBuffer, ArrayPool byteArrayPool)\n        throws IOException;\n  }\n\n  private static void runTest(byte[] data, ParserTestCase test) throws IOException {\n    InputStream is = new ByteArrayInputStream(data);\n    DefaultImageHeaderParser parser = new DefaultImageHeaderParser();\n    test.run(parser, is, new LruArrayPool());\n\n    ByteBuffer buffer = ByteBuffer.wrap(data);\n    parser = new DefaultImageHeaderParser();\n    test.run(parser, buffer, new LruArrayPool());\n  }\n\n  private static byte[] generatePngHeaderWithIhdr(int bitDepth) {\n    byte[] result = new byte[PNG_HEADER_WITH_IHDR_CHUNK.length];\n    System.arraycopy(PNG_HEADER_WITH_IHDR_CHUNK, 0, result, 0, PNG_HEADER_WITH_IHDR_CHUNK.length);\n    result[result.length - 1] = (byte) bitDepth;\n    return result;\n  }\n\n  private static class SometimesZeroSkipInputStream extends FilterInputStream {\n    boolean returnZeroFlag = true;\n\n    SometimesZeroSkipInputStream(InputStream in) {\n      super(in);\n    }\n\n    @Override\n    public long skip(long byteCount) throws IOException {\n      final long result;\n      if (returnZeroFlag) {\n        result = 0;\n      } else {\n        result = super.skip(byteCount);\n      }\n      returnZeroFlag = !returnZeroFlag;\n      return result;\n    }\n  }\n\n  private static class PartialSkipInputStream extends FilterInputStream {\n\n    PartialSkipInputStream(InputStream in) {\n      super(in);\n    }\n\n    @Override\n    public long skip(long byteCount) throws IOException {\n      long toActuallySkip = byteCount / 2;\n      if (byteCount == 1) {\n        toActuallySkip = 1;\n      }\n      return super.skip(toActuallySkip);\n    }\n  }\n\n  private static class PartialReadInputStream extends FilterInputStream {\n\n    PartialReadInputStream(InputStream in) {\n      super(in);\n    }\n\n    @Override\n    public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {\n      int toActuallyRead = byteCount / 2;\n      if (byteCount == 1) {\n        toActuallyRead = 1;\n      }\n      return super.read(buffer, byteOffset, toActuallyRead);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/DownsampleStrategyTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = Config.OLDEST_SDK)\npublic class DownsampleStrategyTest {\n\n  @Test\n  public void testAtMost_withSourceSmallerInOneDimensions_returnsScaleFactorForLargestDimension() {\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(100, 200, 200, 200)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(200, 100, 200, 200)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(270, 480, 724, 440)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(400, 200, 200, 200)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(800, 200, 200, 200)).isEqualTo(1 / 4f);\n  }\n\n  @Test\n  public void testAtMost_withSourceExactlyEqualToRequested_returnsScaleFactorOfOne() {\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(100, 100, 100, 100)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(1234, 452, 1234, 452)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(341, 122, 341, 122)).isEqualTo(1f);\n  }\n\n  @Test\n  public void testAtMost_withSourceLessThanTwiceRequestedSize_returnsScaleFactorOfTwo() {\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(150, 150, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(101, 101, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(199, 199, 100, 100)).isEqualTo(1 / 2f);\n  }\n\n  @Test\n  public void testAtMost_withSourceGreaterThanRequestedSize_returnsPowerOfTwoScaleFactor() {\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(200, 200, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(300, 300, 100, 100)).isEqualTo(1 / 4f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(400, 400, 100, 100)).isEqualTo(1 / 4f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(1000, 200, 100, 100)).isEqualTo(1 / 16f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(1000, 1000, 100, 100)).isEqualTo(1 / 16f);\n  }\n\n  @Test\n  public void testAtMost_withSourceGreaterInOneDimension_returnsScaleFactorOfLargestDimension() {\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(101, 200, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(199, 200, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(400, 200, 100, 100)).isEqualTo(1 / 4f);\n    assertThat(DownsampleStrategy.AT_MOST.getScaleFactor(1000, 400, 100, 100)).isEqualTo(1 / 16f);\n  }\n\n  @Test\n  public void testAtLeast_withSourceSmallerInOneDimension_returnsScaleFactorOfOne() {\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(100, 200, 200, 200)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(200, 100, 200, 200)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(270, 480, 724, 440)).isEqualTo(1f);\n  }\n\n  @Test\n  public void testAtLeast_withSourceExactlyEqualToRequested_returnsScaleFactorOfOne() {\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(100, 100, 100, 100)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(1234, 452, 1234, 452)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(341, 122, 341, 122)).isEqualTo(1f);\n  }\n\n  @Test\n  public void testAtLeast_withSourceLessThanTwiceRequestedSize_returnsScaleFactorOfOne() {\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(150, 150, 100, 100)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(101, 101, 100, 100)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(199, 199, 100, 100)).isEqualTo(1f);\n  }\n\n  @Test\n  public void testAtLeast_withSourceGreaterThanRequestedSize_returnsPowerOfTwoScaleFactor() {\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(200, 200, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(300, 300, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(400, 400, 100, 100)).isEqualTo(1 / 4f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(1000, 200, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(1000, 1000, 100, 100)).isEqualTo(1 / 8f);\n  }\n\n  @Test\n  public void testAtLeast_withSourceGreaterInOneDimension_returnsScaleFactorOfSmallestDimension() {\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(101, 200, 100, 100)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(199, 200, 100, 100)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(400, 200, 100, 100)).isEqualTo(1 / 2f);\n    assertThat(DownsampleStrategy.AT_LEAST.getScaleFactor(1000, 400, 100, 100)).isEqualTo(1 / 4f);\n  }\n\n  @Test\n  public void testCenterInside_scalesImageToFitWithinRequestedBounds() {\n    assertThat(DownsampleStrategy.FIT_CENTER.getScaleFactor(100, 200, 300, 300))\n        .isEqualTo(300 / 200f);\n    assertThat(DownsampleStrategy.FIT_CENTER.getScaleFactor(270, 480, 724, 440))\n        .isEqualTo(440 / 480f);\n    assertThat(DownsampleStrategy.FIT_CENTER.getScaleFactor(100, 100, 100, 100)).isEqualTo(1f);\n  }\n\n  @Test\n  public void testCenterOutside_scalesImageToFitAroundRequestedBounds() {\n    assertThat(DownsampleStrategy.CENTER_OUTSIDE.getScaleFactor(100, 200, 300, 300))\n        .isEqualTo(300 / 100f);\n    assertThat(DownsampleStrategy.CENTER_OUTSIDE.getScaleFactor(270, 480, 724, 440))\n        .isEqualTo(724 / 270f);\n    assertThat(DownsampleStrategy.CENTER_OUTSIDE.getScaleFactor(100, 100, 100, 100)).isEqualTo(1f);\n  }\n\n  @Test\n  public void testNone_alwaysReturnsOne() {\n    assertThat(DownsampleStrategy.NONE.getScaleFactor(100, 100, 100, 100)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.NONE.getScaleFactor(200, 200, 100, 100)).isEqualTo(1f);\n    assertThat(DownsampleStrategy.NONE.getScaleFactor(100, 100, 200, 200)).isEqualTo(1f);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/DrawableTransformationTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.load.resource.SimpleResource;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class DrawableTransformationTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n  @Mock private Transformation<Bitmap> bitmapTransformation;\n  private BitmapPool bitmapPool;\n  private DrawableTransformation transformation;\n  private Context context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    transformation = new DrawableTransformation(bitmapTransformation, /* isRequired= */ true);\n    context = ApplicationProvider.getApplicationContext();\n    bitmapPool = new BitmapPoolAdapter();\n    Glide.init(context, new GlideBuilder().setBitmapPool(bitmapPool));\n  }\n\n  @After\n  public void tearDown() {\n    Glide.tearDown();\n  }\n\n  @Test\n  public void transform_withBitmapDrawable_andUnitBitmapTransformation_doesNotRecycle() {\n    when(bitmapTransformation.transform(\n            any(Context.class), anyBitmapResource(), anyInt(), anyInt()))\n        .thenAnswer(new ReturnGivenResource());\n\n    Bitmap bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n    @SuppressWarnings(\"unchecked\")\n    Resource<Drawable> input =\n        (Resource<Drawable>) (Resource<?>) new BitmapDrawableResource(drawable, bitmapPool);\n    transformation.transform(context, input, /* outWidth= */ 100, /* outHeight= */ 200);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void transform_withBitmapDrawable_andFunctionalBitmapTransformation_doesNotRecycle() {\n    when(bitmapTransformation.transform(\n            any(Context.class), anyBitmapResource(), anyInt(), anyInt()))\n        .thenAnswer(\n            new Answer<Resource<Bitmap>>() {\n              @Override\n              public Resource<Bitmap> answer(InvocationOnMock invocationOnMock) throws Throwable {\n                return BitmapResource.obtain(\n                    Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888), bitmapPool);\n              }\n            });\n    Bitmap bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);\n    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);\n    @SuppressWarnings(\"unchecked\")\n    Resource<Drawable> input =\n        (Resource<Drawable>) (Resource<?>) new BitmapDrawableResource(drawable, bitmapPool);\n    transformation.transform(context, input, /* outWidth= */ 100, /* outHeight= */ 200);\n\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void transform_withColorDrawable_andUnitBitmapTransformation_recycles() {\n    bitmapPool = mock(BitmapPool.class);\n    Glide.tearDown();\n    Glide.init(context, new GlideBuilder().setBitmapPool(bitmapPool));\n    when(bitmapTransformation.transform(\n            any(Context.class), anyBitmapResource(), anyInt(), anyInt()))\n        .thenAnswer(new ReturnGivenResource());\n\n    ColorDrawable colorDrawable = new ColorDrawable(Color.RED);\n    final Resource<Drawable> input = new SimpleResource<Drawable>(colorDrawable);\n\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) throws Throwable {\n                Bitmap bitmap = (Bitmap) invocationOnMock.getArguments()[0];\n                assertThat(bitmap.getWidth()).isEqualTo(100);\n                assertThat(bitmap.getHeight()).isEqualTo(200);\n                return null;\n              }\n            })\n        .when(bitmapPool)\n        .put(any(Bitmap.class));\n    when(bitmapPool.get(anyInt(), anyInt(), any(Bitmap.Config.class)))\n        .thenAnswer(\n            new Answer<Bitmap>() {\n              @Override\n              public Bitmap answer(InvocationOnMock invocationOnMock) throws Throwable {\n                int width = (Integer) invocationOnMock.getArguments()[0];\n                int height = (Integer) invocationOnMock.getArguments()[1];\n                Bitmap.Config config = (Bitmap.Config) invocationOnMock.getArguments()[2];\n                return Bitmap.createBitmap(width, height, config);\n              }\n            });\n\n    transformation.transform(context, input, /* outWidth= */ 100, /* outHeight= */ 200);\n\n    verify(bitmapPool).put(isA(Bitmap.class));\n  }\n\n  @Test\n  public void testEquals() {\n    BitmapTransformation otherBitmapTransformation = mock(BitmapTransformation.class);\n    doAnswer(new Util.WriteDigest(\"bitmapTransformation\"))\n        .when(bitmapTransformation)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    doAnswer(new Util.WriteDigest(\"otherBitmapTransformation\"))\n        .when(otherBitmapTransformation)\n        .updateDiskCacheKey(any(MessageDigest.class));\n\n    keyTester\n        .addEquivalenceGroup(\n            transformation,\n            new DrawableTransformation(bitmapTransformation, /* isRequired= */ true),\n            new DrawableTransformation(bitmapTransformation, /* isRequired= */ false))\n        .addEquivalenceGroup(bitmapTransformation)\n        .addEquivalenceGroup(otherBitmapTransformation)\n        .addEquivalenceGroup(\n            new DrawableTransformation(otherBitmapTransformation, /* isRequired= */ true),\n            new DrawableTransformation(otherBitmapTransformation, /* isRequired= */ false))\n        .addRegressionTest(\n            new DrawableTransformation(bitmapTransformation, /* isRequired= */ true),\n            \"eddf60c557a6315a489b8a3a19b12439a90381256289fbe9a503afa726230bd9\")\n        .addRegressionTest(\n            new DrawableTransformation(otherBitmapTransformation, /* isRequired= */ false),\n            \"40931536ed0ec97c39d4be10c44f5b69a86030ec575317f5a0f17e15a0ea9be8\")\n        .test();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static Resource<Bitmap> anyBitmapResource() {\n    return any(Resource.class);\n  }\n\n  private static final class ReturnGivenResource implements Answer<Resource<Bitmap>> {\n\n    @Override\n    public Resource<Bitmap> answer(InvocationOnMock invocationOnMock) throws Throwable {\n      @SuppressWarnings(\"unchecked\")\n      Resource<Bitmap> input = (Resource<Bitmap>) invocationOnMock.getArguments()[1];\n      return input;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/FitCenterTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class FitCenterTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Mock private Resource<Bitmap> resource;\n  @Mock private Transformation<Bitmap> transformation;\n  private FitCenter fitCenter;\n  private int bitmapWidth;\n  private int bitmapHeight;\n  private Application context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    bitmapWidth = 100;\n    bitmapHeight = 100;\n    Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);\n    when(resource.get()).thenReturn(bitmap);\n\n    BitmapPool pool = new BitmapPoolAdapter();\n    context = ApplicationProvider.getApplicationContext();\n    Glide.init(context, new GlideBuilder().setBitmapPool(pool));\n\n    fitCenter = new FitCenter();\n  }\n\n  @After\n  public void tearDown() {\n    Glide.tearDown();\n  }\n\n  @Test\n  public void testReturnsGivenResourceIfMatchesSizeExactly() {\n    Resource<Bitmap> result = fitCenter.transform(context, resource, bitmapWidth, bitmapHeight);\n\n    assertEquals(resource, result);\n  }\n\n  @Test\n  public void testDoesNotRecycleGivenResourceIfMatchesSizeExactly() {\n    fitCenter.transform(context, resource, bitmapWidth, bitmapHeight);\n\n    verify(resource, never()).recycle();\n  }\n\n  @Test\n  public void testDoesNotRecycleGivenResource() {\n    fitCenter.transform(context, resource, 50, 50);\n\n    verify(resource, never()).recycle();\n  }\n\n  @Test\n  public void testEquals() throws NoSuchAlgorithmException {\n    doAnswer(new Util.WriteDigest(\"other\"))\n        .when(transformation)\n        .updateDiskCacheKey(any(MessageDigest.class));\n    keyTester\n        .addEquivalenceGroup(fitCenter, new FitCenter(), new FitCenter())\n        .addEquivalenceGroup(transformation)\n        .addRegressionTest(\n            new FitCenter(), \"eda03bc6969032145110add4bfe399915897406f4ca3a1a7512d07750e60f90d\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/HardwareConfigStateTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth.assertWithMessage;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.os.Build;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.shadows.ShadowBuild;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(manifest = Config.NONE)\npublic class HardwareConfigStateTest {\n  private static final int VALID_DIMENSION = 100;\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void\n      setHardwareConfigIfAllowed_withAllowedState_setsInPreferredConfigAndMutable_returnsTrue() {\n    HardwareConfigState state = new HardwareConfigState();\n    state.unblockHardwareBitmaps();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ VALID_DIMENSION,\n            /* targetHeight= */ VALID_DIMENSION,\n            options,\n            /* isHardwareConfigAllowed= */ true,\n            /* isExifOrientationRequired= */ false);\n\n    assertThat(result).isTrue();\n    assertThat(options.inPreferredConfig).isEqualTo(Bitmap.Config.HARDWARE);\n  }\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void\n      setHardwareConfigIfAllowed_withAllowedState_afterReblock_returnsFalseAndDoesNotSetValues() {\n    HardwareConfigState state = new HardwareConfigState();\n    state.unblockHardwareBitmaps();\n    state.blockHardwareBitmaps();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ VALID_DIMENSION,\n            /* targetHeight= */ VALID_DIMENSION,\n            options,\n            /* isHardwareConfigAllowed= */ true,\n            /* isExifOrientationRequired= */ false);\n\n    assertThat(result).isFalse();\n    assertThat(options.inPreferredConfig).isNotEqualTo(Bitmap.Config.HARDWARE);\n  }\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void setHardwareConfigIfAllowed_withInvalidWidth_returnsFalse_doesNotSetValues() {\n    HardwareConfigState state = new HardwareConfigState();\n    state.unblockHardwareBitmaps();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inPreferredConfig = null;\n    options.inMutable = true;\n\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ -1,\n            /* targetHeight= */ VALID_DIMENSION,\n            options,\n            /* isHardwareConfigAllowed= */ true,\n            /* isExifOrientationRequired= */ false);\n\n    assertThat(result).isFalse();\n    assertThat(options.inMutable).isTrue();\n    assertThat(options.inPreferredConfig).isNull();\n  }\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void setHardwareConfigIfAllowed_withInvalidHeight_returnsFalse_doesNotSetValues() {\n    HardwareConfigState state = new HardwareConfigState();\n    state.unblockHardwareBitmaps();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inPreferredConfig = null;\n    options.inMutable = true;\n\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ VALID_DIMENSION,\n            /* targetHeight= */ -1,\n            options,\n            /* isHardwareConfigAllowed= */ true,\n            /* isExifOrientationRequired= */ false);\n\n    assertThat(result).isFalse();\n    assertThat(options.inMutable).isTrue();\n    assertThat(options.inPreferredConfig).isNull();\n  }\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void\n      setHardwareConfigIfAllowed_withHardwareConfigDisallowed_returnsFalse_doesNotSetValues() {\n    HardwareConfigState state = new HardwareConfigState();\n    state.unblockHardwareBitmaps();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inPreferredConfig = null;\n    options.inMutable = true;\n\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ VALID_DIMENSION,\n            /* targetHeight= */ VALID_DIMENSION,\n            options,\n            /* isHardwareConfigAllowed= */ false,\n            /* isExifOrientationRequired= */ false);\n\n    assertThat(result).isFalse();\n    assertThat(options.inMutable).isTrue();\n    assertThat(options.inPreferredConfig).isNull();\n  }\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void\n      setHardwareConfigIfAllowed_withExifOrientationRequired_returnsFalse_doesNotSetValues() {\n    HardwareConfigState state = new HardwareConfigState();\n    state.unblockHardwareBitmaps();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inPreferredConfig = null;\n    options.inMutable = true;\n\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ VALID_DIMENSION,\n            /* targetHeight= */ VALID_DIMENSION,\n            options,\n            /* isHardwareConfigAllowed= */ true,\n            /* isExifOrientationRequired= */ true);\n\n    assertThat(result).isFalse();\n    assertThat(options.inMutable).isTrue();\n    assertThat(options.inPreferredConfig).isNull();\n  }\n\n  @Config(sdk = Build.VERSION_CODES.N_MR1)\n  @Test\n  public void setHardwareConfigIfAllowed_withOsLessThanO_returnsFalse_doesNotSetValues() {\n    HardwareConfigState state = new HardwareConfigState();\n    state.unblockHardwareBitmaps();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inPreferredConfig = null;\n    options.inMutable = true;\n\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ VALID_DIMENSION,\n            /* targetHeight= */ VALID_DIMENSION,\n            options,\n            /* isHardwareConfigAllowed= */ true,\n            /* isExifOrientationRequired= */ false);\n\n    assertThat(result).isFalse();\n    assertThat(options.inMutable).isTrue();\n    assertThat(options.inPreferredConfig).isNull();\n  }\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void\n      setHardwareConfigIfAllowed_withOsLessThanQ_beforeUnblockingHardwareBitmaps_returnsFalseAndDoesNotSetValues() {\n    HardwareConfigState state = new HardwareConfigState();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inPreferredConfig = null;\n    options.inMutable = true;\n\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ VALID_DIMENSION,\n            /* targetHeight= */ VALID_DIMENSION,\n            options,\n            /* isHardwareConfigAllowed= */ true,\n            /* isExifOrientationRequired= */ false);\n\n    assertThat(result).isFalse();\n    assertThat(options.inMutable).isTrue();\n    assertThat(options.inPreferredConfig).isNull();\n  }\n\n  @Config(sdk = Build.VERSION_CODES.Q)\n  @Test\n  public void\n      setHardwareConfigIfAllowed_withOsQ_beforeUnblockingHardwareBitmaps_returnsTrueAndSetsValues() {\n    HardwareConfigState state = new HardwareConfigState();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inPreferredConfig = null;\n    options.inMutable = true;\n\n    boolean result =\n        state.setHardwareConfigIfAllowed(\n            /* targetWidth= */ VALID_DIMENSION,\n            /* targetHeight= */ VALID_DIMENSION,\n            options,\n            /* isHardwareConfigAllowed= */ true,\n            /* isExifOrientationRequired= */ false);\n\n    assertThat(result).isTrue();\n    assertThat(options.inMutable).isFalse();\n    assertThat(options.inPreferredConfig).isEqualTo(Bitmap.Config.HARDWARE);\n  }\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void setHardwareConfigIfAllowed_withPreviouslyDisallowedSamsungDevices_P_returnsTrue() {\n    for (String model :\n        new String[] {\n          \"SM-N9351\", \"SM-J72053\", \"SM-G9600\", \"SM-G965ab\", \"SM-G935.\", \"SM-G930\", \"SM-A5204\"\n        }) {\n      ShadowBuild.setModel(model);\n      HardwareConfigState state = new HardwareConfigState();\n      state.unblockHardwareBitmaps();\n      BitmapFactory.Options options = new BitmapFactory.Options();\n      options.inPreferredConfig = null;\n      options.inMutable = true;\n\n      boolean result =\n          state.setHardwareConfigIfAllowed(\n              /* targetWidth= */ VALID_DIMENSION,\n              /* targetHeight= */ VALID_DIMENSION,\n              options,\n              /* isHardwareConfigAllowed= */ true,\n              /* isExifOrientationRequired= */ false);\n\n      assertWithMessage(\"model: \" + model).that(result).isTrue();\n      assertWithMessage(\"model: \" + model).that(options.inMutable).isFalse();\n      assertWithMessage(\"model: \" + model)\n          .that(options.inPreferredConfig)\n          .isEqualTo(Bitmap.Config.HARDWARE);\n    }\n  }\n\n  @Config(sdk = Build.VERSION_CODES.P)\n  @Test\n  public void setHardwareConfigIfAllowed_withShortOrEmptyModelNames_returnsTrue() {\n    for (String model : new String[] {\".\", \"-\", \"\", \"S\", \"SM\", \"SM-\", \"SM-G\", \"SM-G9.\", \"SM-G93\"}) {\n      ShadowBuild.setModel(model);\n      HardwareConfigState state = new HardwareConfigState();\n      state.unblockHardwareBitmaps();\n      BitmapFactory.Options options = new BitmapFactory.Options();\n      options.inPreferredConfig = null;\n      options.inMutable = true;\n\n      boolean result =\n          state.setHardwareConfigIfAllowed(\n              /* targetWidth= */ VALID_DIMENSION,\n              /* targetHeight= */ VALID_DIMENSION,\n              options,\n              /* isHardwareConfigAllowed= */ true,\n              /* isExifOrientationRequired= */ false);\n\n      assertWithMessage(\"model: \" + model).that(result).isTrue();\n      assertWithMessage(\"model: \" + model).that(options.inMutable).isFalse();\n      assertWithMessage(\"model: \" + model)\n          .that(options.inPreferredConfig)\n          .isEqualTo(Bitmap.Config.HARDWARE);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/LazyBitmapDrawableResourceTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.engine.Initializable;\nimport com.bumptech.glide.load.engine.Resource;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class LazyBitmapDrawableResourceTest {\n  @Mock private Resource<Bitmap> bitmapResource;\n  private LazyBitmapDrawableResource resource;\n  private Resources resources;\n  private Bitmap bitmap;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(bitmapResource.get()).thenReturn(bitmap);\n\n    resources = ApplicationProvider.getApplicationContext().getResources();\n    resource =\n        (LazyBitmapDrawableResource) LazyBitmapDrawableResource.obtain(resources, bitmapResource);\n  }\n\n  @Test\n  public void obtain_withNullBitmapResource_returnsNull() {\n    assertThat(LazyBitmapDrawableResource.obtain(resources, null)).isNull();\n  }\n\n  @Test\n  public void getSize_returnsSizeOfWrappedResource() {\n    when(bitmapResource.getSize()).thenReturn(100);\n    assertThat(resource.getSize()).isEqualTo(100);\n  }\n\n  @Test\n  public void recycle_callsRecycleOnWrappedResource() {\n    resource.recycle();\n    verify(bitmapResource).recycle();\n  }\n\n  @Test\n  public void recycle_doesNotRecycleWrappedBitmap() {\n    resource.recycle();\n    assertThat(bitmap.isRecycled()).isFalse();\n  }\n\n  @Test\n  public void get_returnsDrawableContainingWrappedBitmap() {\n    BitmapDrawable drawable = resource.get();\n    assertThat(drawable.getBitmap()).isSameInstanceAs(bitmap);\n  }\n\n  @Test\n  public void initialize_withNonInitializableResource_doesNothing() {\n    resource.initialize();\n  }\n\n  @Test\n  public void initialize_withWrappedInitializableResource_callsInitializeOnWrapped() {\n    InitializableBitmapResource bitmapResource = mock(InitializableBitmapResource.class);\n    resource =\n        (LazyBitmapDrawableResource) LazyBitmapDrawableResource.obtain(resources, bitmapResource);\n    resource.initialize();\n\n    verify(bitmapResource).initialize();\n  }\n\n  private interface InitializableBitmapResource extends Initializable, Resource<Bitmap> {\n    // Intentionally empty.\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStreamTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n// Not required in tests.\n@SuppressWarnings(\"ResultOfMethodCallIgnored\")\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class RecyclableBufferedInputStreamTest {\n\n  private static final int DATA_SIZE = 30;\n  private static final int BUFFER_SIZE = 10;\n\n  private RecyclableBufferedInputStream stream;\n  private byte[] data;\n  private ArrayPool byteArrayPool;\n\n  @Before\n  public void setUp() {\n    data = new byte[DATA_SIZE];\n    for (int i = 0; i < DATA_SIZE; i++) {\n      data[i] = (byte) i;\n    }\n\n    byteArrayPool = new LruArrayPool();\n    InputStream wrapped = new ByteArrayInputStream(data);\n    stream = new RecyclableBufferedInputStream(wrapped, byteArrayPool, BUFFER_SIZE);\n  }\n\n  @Test\n  public void testReturnsTrueForMarkSupported() {\n    assertTrue(stream.markSupported());\n  }\n\n  @Test\n  public void testCanReadIndividualBytes() throws IOException {\n    for (int i = 0; i < data.length; i++) {\n      assertEquals(i, stream.read());\n    }\n    assertEquals(-1, stream.read());\n  }\n\n  @Test\n  public void testCanReadBytesInBulkLargerThanBufferSize() throws IOException {\n    byte[] buffer = new byte[DATA_SIZE];\n    assertEquals(DATA_SIZE, stream.read(buffer, 0, DATA_SIZE));\n    for (int i = 0; i < DATA_SIZE; i++) {\n      assertEquals(i, buffer[i]);\n    }\n  }\n\n  @Test\n  public void testCanReadBytesInBulkSmallerThanBufferSize() throws IOException {\n    int toRead = BUFFER_SIZE / 2;\n    byte[] buffer = new byte[toRead];\n    assertEquals(toRead, stream.read(buffer, 0, toRead));\n    for (int i = 0; i < toRead; i++) {\n      assertEquals(i, buffer[i]);\n    }\n  }\n\n  @Test\n  public void testReadingZeroBytesIntoBufferReadsZeroBytes() throws IOException {\n    // Make sure the next value is not 0.\n    stream.read();\n    byte[] buffer = new byte[BUFFER_SIZE];\n    assertEquals(0, stream.read(buffer, 0, 0));\n\n    for (int i = 0; i < BUFFER_SIZE; i++) {\n      assertEquals(0, buffer[i]);\n    }\n  }\n\n  @Test\n  public void testCanReadIntoBufferLargerThanDataSize() throws IOException {\n    int toRead = DATA_SIZE * 2;\n    byte[] buffer = new byte[toRead];\n    assertEquals(DATA_SIZE, stream.read(buffer, 0, toRead));\n    for (int i = 0; i < DATA_SIZE; i++) {\n      assertEquals(i, buffer[i]);\n    }\n    for (int i = DATA_SIZE; i < toRead; i++) {\n      assertEquals(0, buffer[i]);\n    }\n  }\n\n  @Test\n  public void testCanReadBytesInBulkWithLimit() throws IOException {\n    int toRead = BUFFER_SIZE / 2;\n    byte[] buffer = new byte[BUFFER_SIZE];\n    assertEquals(toRead, stream.read(buffer, 0, toRead));\n\n    // 0, 1, 2, 3, 4, 0, 0, 0, 0, 0\n    for (int i = 0; i < toRead; i++) {\n      assertEquals(i, buffer[i]);\n    }\n    for (int i = toRead; i < BUFFER_SIZE; i++) {\n      assertEquals(0, buffer[i]);\n    }\n  }\n\n  @Test\n  public void testCanReadBytesInBulkWithOffset() throws IOException {\n    int toRead = BUFFER_SIZE / 2;\n    byte[] buffer = new byte[BUFFER_SIZE];\n    assertEquals(toRead, stream.read(buffer, BUFFER_SIZE - toRead, toRead));\n    // 0, 0, 0, 0, 0, 0, 1, 2, 3, 4\n    for (int i = 0; i < toRead; i++) {\n      assertEquals(0, buffer[i]);\n    }\n    for (int i = toRead; i < BUFFER_SIZE; i++) {\n      assertEquals(i - toRead, buffer[i]);\n    }\n  }\n\n  @Test\n  public void testCanReadBytesInBulkWhenSomeButNotAllBytesAreInBuffer() throws IOException {\n    stream.read();\n    byte[] buffer = new byte[BUFFER_SIZE];\n    assertEquals(BUFFER_SIZE, stream.read(buffer, 0, BUFFER_SIZE));\n    for (int i = 1; i < BUFFER_SIZE + 1; i++) {\n      assertEquals(i, buffer[i - 1]);\n    }\n  }\n\n  @Test\n  public void testCanSkipBytes() throws IOException {\n    int toSkip = data.length / 2;\n    assertEquals(toSkip, stream.skip(toSkip));\n    for (int i = toSkip; i < data.length; i++) {\n      assertEquals(i, stream.read());\n    }\n    assertEquals(-1, stream.read());\n  }\n\n  @Test\n  public void testSkipReturnsZeroIfSkipByteCountIsZero() throws IOException {\n    assertEquals(0, stream.skip(0));\n    assertEquals(0, stream.read());\n  }\n\n  @Test\n  public void testSkipReturnsZeroIfSkipByteCountIsNegative() throws IOException {\n    assertEquals(0, stream.skip(-13));\n    assertEquals(0, stream.read());\n  }\n\n  @Test\n  public void testCloseClosesWrappedStream() throws IOException {\n    InputStream wrapped = mock(InputStream.class);\n    stream = new RecyclableBufferedInputStream(wrapped, byteArrayPool);\n    stream.close();\n    verify(wrapped).close();\n  }\n\n  @Test\n  public void testCanSafelyBeClosedMultipleTimes() throws IOException {\n    InputStream wrapped = mock(InputStream.class);\n    stream = new RecyclableBufferedInputStream(wrapped, byteArrayPool);\n    stream.close();\n    stream.close();\n    stream.close();\n\n    verify(wrapped, times(1)).close();\n  }\n\n  @Test\n  public void testCanMarkAndReset() throws IOException {\n    byte[] buffer = new byte[BUFFER_SIZE];\n    stream.mark(BUFFER_SIZE);\n    assertEquals(BUFFER_SIZE, stream.read(buffer, 0, BUFFER_SIZE));\n    for (int i = 0; i < BUFFER_SIZE; i++) {\n      assertEquals(i, buffer[i]);\n    }\n    Arrays.fill(buffer, (byte) 0);\n    stream.reset();\n\n    assertEquals(BUFFER_SIZE, stream.read(buffer, 0, BUFFER_SIZE));\n    for (int i = 0; i < BUFFER_SIZE; i++) {\n      assertEquals(i, buffer[i]);\n    }\n  }\n\n  @Test\n  public void testCanResetRepeatedlyAfterMarking() throws IOException {\n    byte[] buffer = new byte[BUFFER_SIZE];\n    stream.mark(BUFFER_SIZE);\n    for (int repeat = 0; repeat < 10; repeat++) {\n      assertEquals(BUFFER_SIZE, stream.read(buffer, 0, BUFFER_SIZE));\n      for (int i = 0; i < BUFFER_SIZE; i++) {\n        assertEquals(i, buffer[i]);\n      }\n      stream.reset();\n    }\n  }\n\n  @Test\n  public void testCanMarkInMiddleOfBufferAndStillReadUpToBufferLengthBeforeResetting()\n      throws IOException {\n    int markPos = BUFFER_SIZE / 2;\n    for (int i = 0; i < markPos; i++) {\n      stream.read();\n    }\n    stream.mark(BUFFER_SIZE);\n\n    for (int i = 0; i < BUFFER_SIZE; i++) {\n      stream.read();\n    }\n\n    stream.reset();\n    assertEquals(markPos, stream.read());\n  }\n\n  @Test\n  public void testAvailableReturnsWrappedAvailableIfNoBytesRead() throws IOException {\n    assertEquals(DATA_SIZE, stream.available());\n  }\n\n  @Test\n  public void testAvailableIncludesDataInBufferAndWrappedAvailableIfBytesRead() throws IOException {\n    stream.read();\n    assertEquals(DATA_SIZE - 1, stream.available());\n  }\n\n  @Test(expected = IOException.class)\n  public void testCloseThrowsIfWrappedStreamThrowsOnClose() throws IOException {\n    InputStream wrapped = mock(InputStream.class);\n    doThrow(new IOException()).when(wrapped).close();\n    stream = new RecyclableBufferedInputStream(wrapped, byteArrayPool);\n    stream.close();\n  }\n\n  @Test(expected = IOException.class)\n  public void testAvailableThrowsIfStreamIsClosed() throws IOException {\n    stream.close();\n    stream.available();\n  }\n\n  @Test(expected = IOException.class)\n  public void testReadThrowsIfStreamIsClosed() throws IOException {\n    stream.close();\n    stream.read();\n  }\n\n  @Test(expected = IOException.class)\n  public void testReadBulkThrowsIfStreamIsClosed() throws IOException {\n    stream.close();\n    stream.read(new byte[1], 0, 1);\n  }\n\n  @Test(expected = IOException.class)\n  public void testResetThrowsIfStreamIsClosed() throws IOException {\n    stream.close();\n    stream.reset();\n  }\n\n  @Test(expected = IOException.class)\n  public void testSkipThrowsIfStreamIsClosed() throws IOException {\n    stream.close();\n    stream.skip(10);\n  }\n\n  @Test(expected = RecyclableBufferedInputStream.InvalidMarkException.class)\n  public void testResetThrowsIfMarkNotSet() throws IOException {\n    stream.reset();\n  }\n\n  @Test(expected = RecyclableBufferedInputStream.InvalidMarkException.class)\n  public void testResetThrowsIfMarkIsInvalid() throws IOException {\n    stream.mark(1);\n    stream.read(new byte[BUFFER_SIZE], 0, BUFFER_SIZE);\n    stream.read();\n    stream.reset();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/TransformationUtilsTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.ColorSpace;\nimport android.graphics.Matrix;\nimport android.os.Build.VERSION_CODES;\nimport androidx.exifinterface.media.ExifInterface;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.tests.Util;\nimport com.google.common.collect.Range;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = 28)\npublic class TransformationUtilsTest {\n\n  @Mock private BitmapPool bitmapPool;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    when(bitmapPool.get(anyInt(), anyInt(), any(Bitmap.Config.class)))\n        .thenAnswer(new Util.CreateBitmap());\n  }\n\n  @Test\n  public void testFitCenterWithWideBitmap() {\n    final int maxSide = 500;\n\n    Bitmap wide = Bitmap.createBitmap(2000, 100, Bitmap.Config.ARGB_8888);\n\n    Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, wide, maxSide, maxSide);\n\n    assertHasOriginalAspectRatio(wide, transformed);\n    assertBitmapFitsExactlyWithinBounds(maxSide, transformed);\n  }\n\n  @Test\n  public void testFitCenterWithSmallWideBitmap() {\n    final int maxSide = 500;\n\n    Bitmap smallWide = Bitmap.createBitmap(400, 40, Bitmap.Config.ARGB_8888);\n\n    Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, smallWide, maxSide, maxSide);\n\n    assertHasOriginalAspectRatio(smallWide, transformed);\n    assertBitmapFitsExactlyWithinBounds(maxSide, transformed);\n  }\n\n  @Test\n  public void testFitCenterWithTallBitmap() {\n    final int maxSide = 500;\n\n    Bitmap tall = Bitmap.createBitmap(65, 3000, Bitmap.Config.ARGB_8888);\n\n    Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, tall, maxSide, maxSide);\n\n    assertHasOriginalAspectRatio(tall, transformed);\n    assertBitmapFitsExactlyWithinBounds(maxSide, transformed);\n  }\n\n  @Test\n  public void testFitCenterWithSmallTallBitmap() {\n    final int maxSide = 500;\n\n    Bitmap smallTall = Bitmap.createBitmap(10, 400, Bitmap.Config.ARGB_8888);\n\n    Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, smallTall, maxSide, maxSide);\n\n    assertHasOriginalAspectRatio(smallTall, transformed);\n    assertBitmapFitsExactlyWithinBounds(maxSide, transformed);\n  }\n\n  @Test\n  public void testFitCenterWithSquareBitmap() {\n    final int maxSide = 500;\n\n    Bitmap square = Bitmap.createBitmap(600, 600, Bitmap.Config.ARGB_8888);\n    Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, square, maxSide, maxSide);\n\n    assertHasOriginalAspectRatio(square, transformed);\n    assertBitmapFitsExactlyWithinBounds(maxSide, transformed);\n  }\n\n  @Test\n  public void testFitCenterWithTooSmallSquareBitmap() {\n    final int maxSide = 500;\n\n    Bitmap smallSquare = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, smallSquare, maxSide, maxSide);\n\n    assertHasOriginalAspectRatio(smallSquare, transformed);\n    assertBitmapFitsExactlyWithinBounds(maxSide, transformed);\n  }\n\n  // Test for Issue #195.\n  @Test\n  public void testFitCenterUsesFloorInsteadOfRoundingForOutputBitmapSize() {\n    Bitmap toTransform = Bitmap.createBitmap(1230, 1640, Bitmap.Config.RGB_565);\n\n    Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, toTransform, 1075, 1366);\n\n    assertEquals(1024, transformed.getWidth());\n    assertEquals(1366, transformed.getHeight());\n  }\n\n  @Test\n  public void testFitCenterReturnsGivenBitmapIfGivenBitmapMatchesExactly() {\n    Bitmap toFit = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);\n    Bitmap transformed =\n        TransformationUtils.fitCenter(bitmapPool, toFit, toFit.getWidth(), toFit.getHeight());\n    assertTrue(toFit == transformed);\n  }\n\n  @Test\n  public void testFitCenterReturnsGivenBitmapIfGivenBitmapWidthMatchesExactly() {\n    Bitmap toFit = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);\n    Bitmap transformed =\n        TransformationUtils.fitCenter(bitmapPool, toFit, toFit.getWidth(), toFit.getHeight() * 2);\n    assertTrue(toFit == transformed);\n  }\n\n  @Test\n  public void testFitCenterReturnsGivenBitmapIfGivenBitmapHeightMatchesExactly() {\n    Bitmap toFit = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);\n    Bitmap transformed =\n        TransformationUtils.fitCenter(bitmapPool, toFit, toFit.getWidth() * 2, toFit.getHeight());\n    assertTrue(toFit == transformed);\n  }\n\n  @Test\n  public void testCenterCropReturnsGivenBitmapIfGivenBitmapExactlyMatchesGivenDimensions() {\n    Bitmap toCrop = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888);\n    Bitmap transformed =\n        TransformationUtils.centerCrop(bitmapPool, toCrop, toCrop.getWidth(), toCrop.getHeight());\n\n    // Robolectric incorrectly implements equals() for Bitmaps, we want the original object not\n    // just an equivalent.\n    assertTrue(toCrop == transformed);\n  }\n\n  @Test\n  @Config(sdk = 19)\n  public void testFitCenterHandlesBitmapsWithNullConfigs() {\n    Bitmap toFit = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);\n    toFit.setConfig(null);\n    Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, toFit, 50, 50);\n    assertEquals(Bitmap.Config.ARGB_8888, transformed.getConfig());\n  }\n\n  @Test\n  public void testCenterCropSetsOutBitmapToHaveAlphaIfInBitmapHasAlphaAndOutBitmapIsReused() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);\n    reset(bitmapPool);\n    when(bitmapPool.get(eq(50), eq(50), eq(Bitmap.Config.ARGB_8888))).thenReturn(toReuse);\n\n    toReuse.setHasAlpha(false);\n    toTransform.setHasAlpha(true);\n\n    Bitmap result =\n        TransformationUtils.centerCrop(\n            bitmapPool, toTransform, toReuse.getWidth(), toReuse.getHeight());\n\n    assertEquals(toReuse, result);\n    assertTrue(result.hasAlpha());\n  }\n\n  @Test\n  public void\n      testCenterCropSetsOutBitmapToNotHaveAlphaIfInBitmapDoesNotHaveAlphaAndOutBitmapIsReused() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);\n    reset(bitmapPool);\n    when(bitmapPool.get(eq(50), eq(50), eq(Bitmap.Config.ARGB_8888))).thenReturn(toReuse);\n\n    toReuse.setHasAlpha(true);\n    toTransform.setHasAlpha(false);\n\n    Bitmap result =\n        TransformationUtils.centerCrop(\n            bitmapPool, toTransform, toReuse.getWidth(), toReuse.getHeight());\n\n    assertEquals(toReuse, result);\n    assertFalse(result.hasAlpha());\n  }\n\n  @Test\n  public void testCenterCropSetsOutBitmapToHaveAlphaIfInBitmapHasAlpha() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    toTransform.setHasAlpha(true);\n\n    Bitmap result =\n        TransformationUtils.centerCrop(\n            bitmapPool, toTransform, toTransform.getWidth() / 2, toTransform.getHeight() / 2);\n\n    assertTrue(result.hasAlpha());\n  }\n\n  @Test\n  @Config(sdk = 19)\n  public void testCenterCropHandlesBitmapsWithNullConfigs() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);\n    toTransform.setConfig(null);\n\n    Bitmap transformed = TransformationUtils.centerCrop(bitmapPool, toTransform, 50, 50);\n\n    assertEquals(Bitmap.Config.ARGB_8888, transformed.getConfig());\n  }\n\n  @Test\n  public void testCenterCropSetsOutBitmapToNotHaveAlphaIfInBitmapDoesNotHaveAlpha() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    toTransform.setHasAlpha(false);\n\n    Bitmap result =\n        TransformationUtils.centerCrop(\n            bitmapPool, toTransform, toTransform.getWidth() / 2, toTransform.getHeight() / 2);\n\n    assertFalse(result.hasAlpha());\n  }\n\n  @Test\n  public void testFitCenterSetsOutBitmapToHaveAlphaIfInBitmapHasAlphaAndOutBitmapIsReused() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);\n    reset(bitmapPool);\n    when(bitmapPool.get(eq(toReuse.getWidth()), eq(toReuse.getHeight()), eq(toReuse.getConfig())))\n        .thenReturn(toReuse);\n\n    toReuse.setHasAlpha(false);\n    toTransform.setHasAlpha(true);\n\n    Bitmap result =\n        TransformationUtils.fitCenter(\n            bitmapPool, toTransform, toReuse.getWidth(), toReuse.getHeight());\n\n    assertEquals(toReuse, result);\n    assertTrue(result.hasAlpha());\n  }\n\n  @Test\n  public void\n      testFitCenterSetsOutBitmapToNotHaveAlphaIfInBitmapDoesNotHaveAlphaAndOutBitmapIsReused() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);\n    reset(bitmapPool);\n    when(bitmapPool.get(eq(toReuse.getWidth()), eq(toReuse.getHeight()), eq(toReuse.getConfig())))\n        .thenReturn(toReuse);\n\n    toReuse.setHasAlpha(true);\n    toTransform.setHasAlpha(false);\n\n    Bitmap result =\n        TransformationUtils.fitCenter(\n            bitmapPool, toTransform, toReuse.getWidth(), toReuse.getHeight());\n\n    assertEquals(toReuse, result);\n    assertFalse(result.hasAlpha());\n  }\n\n  @Test\n  public void testFitCenterSetsOutBitmapToHaveAlphaIfInBitmapHasAlpha() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    toTransform.setHasAlpha(true);\n\n    Bitmap result =\n        TransformationUtils.fitCenter(\n            bitmapPool, toTransform, toTransform.getWidth() / 2, toTransform.getHeight() / 2);\n\n    assertTrue(result.hasAlpha());\n  }\n\n  @Test\n  public void testFitCenterSetsOutBitmapToNotHaveAlphaIfInBitmapDoesNotHaveAlpha() {\n    Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    toTransform.setHasAlpha(false);\n\n    Bitmap result =\n        TransformationUtils.fitCenter(\n            bitmapPool, toTransform, toTransform.getWidth() / 2, toTransform.getHeight() / 2);\n\n    assertFalse(result.hasAlpha());\n  }\n\n  private static void assertHasOriginalAspectRatio(Bitmap original, Bitmap transformed) {\n    double originalAspectRatio = (double) original.getWidth() / (double) original.getHeight();\n    double transformedAspectRatio =\n        (double) transformed.getWidth() / (double) transformed.getHeight();\n\n    assertThat(transformedAspectRatio)\n        .isIn(Range.open(originalAspectRatio - 0.05f, originalAspectRatio + 0.05f));\n  }\n\n  private static void assertBitmapFitsExactlyWithinBounds(int maxSide, Bitmap bitmap) {\n    final int width = bitmap.getWidth();\n    final int height = bitmap.getHeight();\n\n    assertThat(width).isIn(Range.atMost(maxSide));\n    assertThat(height).isIn(Range.atMost(maxSide));\n\n    assertTrue(\"one side must match maxSide\", width == maxSide || height == maxSide);\n  }\n\n  @Test\n  public void testGetExifOrientationDegrees() {\n    assertEquals(\n        0, TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_NORMAL));\n    assertEquals(\n        90, TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_TRANSPOSE));\n    assertEquals(\n        90, TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_ROTATE_90));\n    assertEquals(\n        180, TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_ROTATE_180));\n    assertEquals(\n        180,\n        TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_FLIP_VERTICAL));\n    assertEquals(\n        270, TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_TRANSVERSE));\n    assertEquals(\n        270, TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_ROTATE_270));\n  }\n\n  @Test\n  public void testRotateImage() {\n    Bitmap toRotate = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888);\n    toRotate.setPixel(0, 0, Color.BLUE);\n    toRotate.setPixel(0, 1, Color.RED);\n    Bitmap zero = TransformationUtils.rotateImage(toRotate, 0);\n    assertTrue(toRotate == zero);\n\n    Bitmap ninety = TransformationUtils.rotateImage(toRotate, 90);\n    // Checks if native graphics is enabled.\n    if (System.getProperty(\"robolectric.graphicsMode\", \"\").equals(\"NATIVE\")) {\n      assertThat(ninety.getPixel(0, 0)).isEqualTo(Color.RED);\n      assertThat(ninety.getPixel(1, 0)).isEqualTo(Color.BLUE);\n    } else {\n      // Use legacy shadow APIs\n      assertThat(Shadows.shadowOf(ninety).getDescription()).contains(\"rotate=90.0\");\n    }\n    assertEquals(toRotate.getWidth(), toRotate.getHeight());\n  }\n\n  @Test\n  public void testRotateImageExifReturnsGivenBitmapIfRotationIsNormal() {\n    Bitmap toRotate = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);\n    // Use assertTrue because Robolectric incorrectly implements equality for Bitmaps. We want\n    // not just an identical Bitmap, but our original Bitmap object back.\n    Bitmap rotated =\n        TransformationUtils.rotateImageExif(bitmapPool, toRotate, ExifInterface.ORIENTATION_NORMAL);\n    assertTrue(toRotate == rotated);\n  }\n\n  @Test\n  public void testRotateImageExifReturnsGivenBitmapIfRotationIsUndefined() {\n    Bitmap toRotate = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);\n    // Use assertTrue because Robolectric incorrectly implements equality for Bitmaps. We want\n    // not just an identical Bitmap, but our original Bitmap object back.\n    Bitmap rotated =\n        TransformationUtils.rotateImageExif(\n            bitmapPool, toRotate, ExifInterface.ORIENTATION_UNDEFINED);\n    assertTrue(toRotate == rotated);\n  }\n\n  @Test\n  public void testRotateImageExifReturnsGivenBitmapIfOrientationIsInvalid() {\n    Bitmap toRotate = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888);\n    // Use assertTrue because Robolectric incorrectly implements equality for Bitmaps. We want\n    // not just an identical Bitmap, but our original Bitmap object back.\n    Bitmap rotated = TransformationUtils.rotateImageExif(bitmapPool, toRotate, -1);\n    assertTrue(toRotate == rotated);\n  }\n\n  @Test\n  @Config(sdk = 19)\n  public void testRotateImageExif_preservesitmapsWithNullConfigs() {\n    Bitmap toRotate = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);\n    toRotate.setConfig(null);\n    Bitmap rotated =\n        TransformationUtils.rotateImageExif(\n            bitmapPool, toRotate, ExifInterface.ORIENTATION_ROTATE_180);\n    assertNull(rotated.getConfig());\n  }\n\n  @Test\n  @Config(sdk = VERSION_CODES.UPSIDE_DOWN_CAKE)\n  public void rotateImageExif_preservesColorSpace() {\n    Bitmap toRotate = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888);\n    toRotate.setColorSpace(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));\n\n    Bitmap rotated =\n        TransformationUtils.rotateImageExif(\n            bitmapPool, toRotate, ExifInterface.ORIENTATION_ROTATE_90);\n\n    assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), rotated.getColorSpace());\n  }\n\n  // TODO: Add gainmap-based tests once Robolectric has sufficient support.\n\n  @Test\n  public void testInitializeMatrixSetsScaleIfFlipHorizontal() {\n    Matrix matrix = mock(Matrix.class);\n    TransformationUtils.initializeMatrixForRotation(\n        ExifInterface.ORIENTATION_FLIP_HORIZONTAL, matrix);\n    verify(matrix).setScale(-1, 1);\n  }\n\n  @Test\n  public void testInitializeMatrixSetsScaleAndRotateIfFlipVertical() {\n    Matrix matrix = mock(Matrix.class);\n    TransformationUtils.initializeMatrixForRotation(\n        ExifInterface.ORIENTATION_FLIP_VERTICAL, matrix);\n    verify(matrix).setRotate(180);\n    verify(matrix).postScale(-1, 1);\n  }\n\n  @Test\n  public void testInitializeMatrixSetsScaleAndRotateIfTranspose() {\n    Matrix matrix = mock(Matrix.class);\n    TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_TRANSPOSE, matrix);\n    verify(matrix).setRotate(90);\n    verify(matrix).postScale(-1, 1);\n  }\n\n  @Test\n  public void testInitializeMatrixSetsScaleAndRotateIfTransverse() {\n    Matrix matrix = mock(Matrix.class);\n    TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_TRANSVERSE, matrix);\n    verify(matrix).setRotate(-90);\n    verify(matrix).postScale(-1, 1);\n  }\n\n  @Test\n  public void testInitializeMatrixSetsRotateOnRotation() {\n    Matrix matrix = mock(Matrix.class);\n    TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_ROTATE_90, matrix);\n    verify(matrix).setRotate(90);\n    TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_ROTATE_180, matrix);\n    verify(matrix).setRotate(180);\n    TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_ROTATE_270, matrix);\n    verify(matrix).setRotate(-90);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/VideoDecoderTest.java",
    "content": "package com.bumptech.glide.load.resource.bitmap;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport android.media.MediaMetadataRetriever;\nimport android.os.Build;\nimport android.os.Build.VERSION_CODES;\nimport android.os.ParcelFileDescriptor;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.tests.Util;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.util.ReflectionHelpers;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = VERSION_CODES.O_MR1)\npublic class VideoDecoderTest {\n  @Mock private ParcelFileDescriptor resource;\n  @Mock private VideoDecoder.MediaMetadataRetrieverFactory factory;\n  @Mock private VideoDecoder.MediaInitializer<ParcelFileDescriptor> initializer;\n  @Mock private MediaMetadataRetriever retriever;\n  @Mock private BitmapPool bitmapPool;\n  private VideoDecoder<ParcelFileDescriptor> decoder;\n  private Options options;\n  private int initialSdkVersion;\n  private String initialMake;\n  private String initialModel;\n  private String initialBuildId;\n  private String initialDevice;\n\n  @Before\n  public void setup() {\n    MockitoAnnotations.initMocks(this);\n    when(factory.build()).thenReturn(retriever);\n    decoder = new VideoDecoder<>(bitmapPool, initializer, factory);\n    options = new Options();\n\n    initialSdkVersion = Build.VERSION.SDK_INT;\n    initialMake = Build.MANUFACTURER;\n    initialModel = Build.MODEL;\n    initialBuildId = Build.ID;\n    initialDevice = Build.DEVICE;\n  }\n\n  @After\n  public void tearDown() {\n    Util.setSdkVersionInt(initialSdkVersion);\n    resetBuildInfo(initialMake, initialModel, initialBuildId, initialDevice);\n  }\n\n  @Test\n  public void testReturnsRetrievedFrameForResource() throws IOException {\n    Util.setSdkVersionInt(19);\n    Bitmap expected = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(retriever.getFrameAtTime(VideoDecoder.DEFAULT_FRAME, VideoDecoder.DEFAULT_FRAME_OPTION))\n        .thenReturn(expected);\n\n    Resource<Bitmap> result =\n        Preconditions.checkNotNull(decoder.decode(resource, 100, 100, options));\n\n    verify(initializer).initializeRetriever(retriever, resource);\n    assertEquals(expected, result.get());\n  }\n\n  @Test\n  public void testReleasesMediaMetadataRetriever() {\n    Util.setSdkVersionInt(19);\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws IOException {\n            decoder.decode(resource, 1, 2, options);\n          }\n        });\n    try {\n      verify(retriever).release();\n    } catch (Exception e) {\n      // Ignore failures while cleaning up.\n    }\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsExceptionIfCalledWithInvalidFrame() throws IOException {\n    Util.setSdkVersionInt(19);\n    options.set(VideoDecoder.TARGET_FRAME, -5L);\n    new VideoDecoder<>(bitmapPool, initializer, factory).decode(resource, 100, 100, options);\n  }\n\n  @Test\n  public void testSpecifiesThumbnailFrameIfICalledWithFrameNumber() {\n    Util.setSdkVersionInt(19);\n    long frame = 5;\n    options.set(VideoDecoder.TARGET_FRAME, frame);\n    decoder = new VideoDecoder<>(bitmapPool, initializer, factory);\n\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws IOException {\n            decoder.decode(resource, 1, 2, options);\n          }\n        });\n\n    verify(retriever).getFrameAtTime(frame, VideoDecoder.DEFAULT_FRAME_OPTION);\n  }\n\n  @Test\n  public void testDoesNotSpecifyThumbnailFrameIfCalledWithoutFrameNumber() {\n    Util.setSdkVersionInt(19);\n    decoder = new VideoDecoder<>(bitmapPool, initializer, factory);\n    assertThrows(\n        RuntimeException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws IOException {\n            decoder.decode(resource, 100, 100, options);\n          }\n        });\n\n    verify(retriever).getFrameAtTime(VideoDecoder.DEFAULT_FRAME, VideoDecoder.DEFAULT_FRAME_OPTION);\n  }\n\n  @Test\n  public void getScaledFrameAtTime() throws IOException {\n    // Anything other than NONE.\n    options.set(DownsampleStrategy.OPTION, DownsampleStrategy.AT_LEAST);\n\n    Bitmap expected = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH))\n        .thenReturn(\"100\");\n    when(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT))\n        .thenReturn(\"100\");\n    when(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION))\n        .thenReturn(\"0\");\n    when(retriever.getScaledFrameAtTime(-1, MediaMetadataRetriever.OPTION_CLOSEST_SYNC, 100, 100))\n        .thenReturn(expected);\n\n    assertThat(decoder.decode(resource, 100, 100, options).get()).isSameInstanceAs(expected);\n  }\n\n  @Test\n  public void decodeFrame_withTargetSizeOriginal_onApi27_doesNotThrow() throws IOException {\n    Bitmap expected = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(retriever.getFrameAtTime(-1, MediaMetadataRetriever.OPTION_CLOSEST_SYNC))\n        .thenReturn(expected);\n\n    verify(retriever, never()).getScaledFrameAtTime(anyLong(), anyInt(), anyInt(), anyInt());\n    assertThat(decoder.decode(resource, Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL, options).get())\n        .isSameInstanceAs(expected);\n  }\n\n  @Test\n  public void decodeFrame_withTargetSizeOriginalWidthOnly_onApi27_doesNotThrow()\n      throws IOException {\n    Bitmap expected = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(retriever.getFrameAtTime(-1, MediaMetadataRetriever.OPTION_CLOSEST_SYNC))\n        .thenReturn(expected);\n\n    verify(retriever, never()).getScaledFrameAtTime(anyLong(), anyInt(), anyInt(), anyInt());\n    assertThat(decoder.decode(resource, Target.SIZE_ORIGINAL, 100, options).get())\n        .isSameInstanceAs(expected);\n  }\n\n  @Test\n  public void decodeFrame_withTargetSizeOriginalHeightOnly_onApi27_doesNotThrow()\n      throws IOException {\n    Bitmap expected = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(retriever.getFrameAtTime(-1, MediaMetadataRetriever.OPTION_CLOSEST_SYNC))\n        .thenReturn(expected);\n\n    verify(retriever, never()).getScaledFrameAtTime(anyLong(), anyInt(), anyInt(), anyInt());\n    assertThat(decoder.decode(resource, 100, Target.SIZE_ORIGINAL, options).get())\n        .isSameInstanceAs(expected);\n  }\n\n  @Test\n  public void decodeFrame_notArcDeviceButWebm_doesNotInitializeMediaExtractor() throws IOException {\n    setDevice(\"notArc\");\n    when(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE))\n        .thenReturn(\"video/webm\");\n    when(retriever.getFrameAtTime(-1, MediaMetadataRetriever.OPTION_CLOSEST_SYNC))\n        .thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n\n    decoder.decode(resource, Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL, options).get();\n\n    verify(initializer, never()).initializeExtractor(any(), any());\n  }\n\n  @Test\n  public void decodeFrame_arcDeviceButNotWebm_doesNotInitializeMediaExtractor() throws IOException {\n    setDevice(\"arc_cheets\");\n    when(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE))\n        .thenReturn(\"video/mp4\");\n    when(retriever.getFrameAtTime(-1, MediaMetadataRetriever.OPTION_CLOSEST_SYNC))\n        .thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n\n    decoder.decode(resource, Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL, options).get();\n\n    verify(initializer, never()).initializeExtractor(any(), any());\n  }\n\n  @Test\n  public void decodeFrame_arcDeviceAndWebm_initializesMediaExtractor() throws IOException {\n    setDevice(\"arc_cheets\");\n    when(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE))\n        .thenReturn(\"video/webm\");\n    when(retriever.getFrameAtTime(-1, MediaMetadataRetriever.OPTION_CLOSEST_SYNC))\n        .thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n\n    decoder.decode(resource, Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL, options).get();\n\n    verify(initializer).initializeExtractor(any(), any());\n  }\n\n  @Test\n  @Config(sdk = VERSION_CODES.M)\n  public void isHdr180RotationFixRequired_androidM_returnsFalse() {\n    assertThat(VideoDecoder.isHdr180RotationFixRequired()).isFalse();\n  }\n\n  @Test\n  @Config(sdk = VERSION_CODES.Q)\n  public void isHdr180RotationFixRequired_androidQ_returnsFalse() {\n    assertThat(VideoDecoder.isHdr180RotationFixRequired()).isFalse();\n  }\n\n  @Test\n  @Config(sdk = VERSION_CODES.R)\n  public void isHdr180RotationFixRequired_androidR_returnsTrue() {\n    assertThat(VideoDecoder.isHdr180RotationFixRequired()).isTrue();\n  }\n\n  @Test\n  @Config(sdk = VERSION_CODES.S)\n  public void isHdr180RotationFixRequired_androidS_returnsTrue() {\n    assertThat(VideoDecoder.isHdr180RotationFixRequired()).isTrue();\n  }\n\n  private void resetBuildInfo(String make, String model, String buildId, String device) {\n    ReflectionHelpers.setStaticField(Build.class, \"MANUFACTURER\", make);\n    ReflectionHelpers.setStaticField(Build.class, \"MODEL\", model);\n    ReflectionHelpers.setStaticField(Build.class, \"ID\", buildId);\n    setDevice(device);\n  }\n\n  private void setDevice(String device) {\n    ReflectionHelpers.setStaticField(Build.class, \"DEVICE\", device);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/bytes/BytesResourceTest.java",
    "content": "package com.bumptech.glide.load.resource.bytes;\n\nimport static org.junit.Assert.assertEquals;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class BytesResourceTest {\n\n  @Test\n  public void testReturnsGivenBytes() {\n    byte[] bytes = new byte[0];\n    BytesResource resource = new BytesResource(bytes);\n\n    assertEquals(bytes, resource.get());\n  }\n\n  @Test\n  public void testReturnsSizeOfGivenBytes() {\n    byte[] bytes = new byte[123];\n    BytesResource resource = new BytesResource(bytes);\n\n    assertEquals(bytes.length, resource.getSize());\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenNullBytes() {\n    new BytesResource(null);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/drawable/DrawableResourceTest.java",
    "content": "package com.bumptech.glide.load.resource.drawable;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertNotEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class DrawableResourceTest {\n  private TestDrawable drawable;\n  private DrawableResource<TestDrawable> resource;\n\n  @Before\n  public void setUp() {\n    drawable = mock(TestDrawable.class);\n    resource =\n        new DrawableResource<TestDrawable>(drawable) {\n          @NonNull\n          @Override\n          public Class<TestDrawable> getResourceClass() {\n            return TestDrawable.class;\n          }\n\n          @Override\n          public int getSize() {\n            return 0;\n          }\n\n          @Override\n          public void recycle() {}\n        };\n  }\n\n  @Test\n  public void testDoesNotReturnOriginalDrawableOnGet() {\n    when(drawable.getConstantState()).thenReturn(mock(Drawable.ConstantState.class));\n    assertNotEquals(drawable, resource.get());\n  }\n\n  @SuppressWarnings(\"TruthIncompatibleType\")\n  @Test\n  public void testReturnsNewDrawableOnGet() {\n    GifDrawable expected = mock(GifDrawable.class);\n    Drawable.ConstantState constantState = mock(Drawable.ConstantState.class);\n    when(constantState.newDrawable()).thenReturn(expected);\n    when(drawable.getConstantState()).thenReturn(constantState);\n\n    assertThat(resource.get())\n        .isEqualTo(/* expected: TestDrawable, actual: GifDrawable */ expected);\n\n    verify(drawable).getConstantState();\n    verify(constantState).newDrawable();\n  }\n\n  @Test\n  public void get_withNullState_returnsOriginalDrawable() {\n    when(drawable.getConstantState()).thenReturn(null);\n\n    assertThat(resource.get()).isEqualTo(drawable);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfDrawableIsNull() {\n    new DrawableResource<TestDrawable>(null) {\n      @NonNull\n      @Override\n      public Class<TestDrawable> getResourceClass() {\n        return TestDrawable.class;\n      }\n\n      @Override\n      public int getSize() {\n        return 0;\n      }\n\n      @Override\n      public void recycle() {}\n    };\n  }\n\n  /** Just to have a type to test with which is not directly Drawable */\n  private static class TestDrawable extends Drawable {\n    @Override\n    public void draw(@NonNull Canvas canvas) {}\n\n    @Override\n    public void setAlpha(int alpha) {}\n\n    @Override\n    public void setColorFilter(ColorFilter cf) {}\n\n    @Override\n    public int getOpacity() {\n      return 0;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/file/FileDecoderTest.java",
    "content": "package com.bumptech.glide.load.resource.file;\n\nimport static org.junit.Assert.assertEquals;\n\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.File;\nimport java.io.IOException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class FileDecoderTest {\n\n  private FileDecoder decoder;\n  private Options options;\n\n  @Before\n  public void setUp() {\n    decoder = new FileDecoder();\n    options = new Options();\n  }\n\n  @Test\n  public void testReturnsGivenFileAsResource() throws IOException {\n    File expected = new File(\"testFile\");\n    Resource<File> decoded = Preconditions.checkNotNull(decoder.decode(expected, 1, 1, options));\n\n    assertEquals(expected, decoded.get());\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/file/FileResourceTest.java",
    "content": "package com.bumptech.glide.load.resource.file;\n\nimport static org.junit.Assert.assertEquals;\n\nimport java.io.File;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class FileResourceTest {\n\n  private File file;\n  private FileResource resource;\n\n  @Before\n  public void setUp() {\n    file = new File(\"Test\");\n    resource = new FileResource(file);\n  }\n\n  @Test\n  public void testReturnsGivenFile() {\n    assertEquals(file, resource.get());\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/gif/ByteBufferGifDecoderTest.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.fail;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.gifdecoder.GifHeader;\nimport com.bumptech.glide.gifdecoder.GifHeaderParser;\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ByteBufferGifDecoderTest {\n  private static final byte[] GIF_HEADER = new byte[] {0x47, 0x49, 0x46};\n  private static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;\n\n  private ByteBufferGifDecoder decoder;\n  private GifHeader gifHeader;\n  private Options options;\n\n  @Mock private BitmapPool bitmapPool;\n  @Mock private GifHeaderParser parser;\n  @Mock private GifDecoder gifDecoder;\n  @Mock private ByteBufferGifDecoder.GifHeaderParserPool parserPool;\n  @Mock private ByteBufferGifDecoder.GifDecoderFactory decoderFactory;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    gifHeader = Mockito.spy(new GifHeader());\n    when(parser.parseHeader()).thenReturn(gifHeader);\n    when(parserPool.obtain(isA(ByteBuffer.class))).thenReturn(parser);\n\n    when(decoderFactory.build(\n            isA(GifDecoder.BitmapProvider.class), eq(gifHeader), isA(ByteBuffer.class), anyInt()))\n        .thenReturn(gifDecoder);\n\n    List<ImageHeaderParser> parsers = new ArrayList<>();\n    parsers.add(new DefaultImageHeaderParser());\n\n    options = new Options();\n    decoder =\n        new ByteBufferGifDecoder(\n            ApplicationProvider.getApplicationContext(),\n            parsers,\n            bitmapPool,\n            new LruArrayPool(ARRAY_POOL_SIZE_BYTES),\n            parserPool,\n            decoderFactory);\n  }\n\n  @Test\n  public void testDoesNotHandleStreamIfEnabledButNotAGif() throws IOException {\n    assertThat(decoder.handles(ByteBuffer.allocate(0), options)).isFalse();\n  }\n\n  @Test\n  public void testHandlesStreamIfContainsGifHeaderAndDisabledIsNotSet() throws IOException {\n    assertThat(decoder.handles(ByteBuffer.wrap(GIF_HEADER), options)).isTrue();\n  }\n\n  @Test\n  public void testHandlesStreamIfContainsGifHeaderAndDisabledIsFalse() throws IOException {\n    options.set(GifOptions.DISABLE_ANIMATION, false);\n    assertThat(decoder.handles(ByteBuffer.wrap(GIF_HEADER), options)).isTrue();\n  }\n\n  @Test\n  public void testDoesNotHandleStreamIfDisabled() throws IOException {\n    options.set(GifOptions.DISABLE_ANIMATION, true);\n    assertThat(decoder.handles(ByteBuffer.wrap(GIF_HEADER), options)).isFalse();\n  }\n\n  @Test\n  public void testReturnsNullIfParsedHeaderHasZeroFrames() throws IOException {\n    when(gifHeader.getNumFrames()).thenReturn(0);\n\n    assertNull(decoder.decode(ByteBuffer.allocate(10), 100, 100, options));\n  }\n\n  @Test\n  public void testReturnsNullIfParsedHeaderHasFormatError() {\n    when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_FORMAT_ERROR);\n\n    assertNull(decoder.decode(ByteBuffer.allocate(10), 100, 100, options));\n  }\n\n  @Test\n  public void testReturnsNullIfParsedHeaderHasOpenError() {\n    when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_OPEN_ERROR);\n\n    assertNull(decoder.decode(ByteBuffer.allocate(10), 100, 100, options));\n  }\n\n  @Test\n  public void testReturnsParserToPool() throws IOException {\n    decoder.decode(ByteBuffer.allocate(10), 100, 100, options);\n    verify(parserPool).release(eq(parser));\n  }\n\n  @Test\n  public void testReturnsParserToPoolWhenParserThrows() {\n    when(parser.parseHeader()).thenThrow(new RuntimeException(\"Test\"));\n    try {\n      decoder.decode(ByteBuffer.allocate(10), 100, 100, options);\n      fail(\"Failed to receive expected exception\");\n    } catch (RuntimeException e) {\n      // Expected.\n    }\n\n    verify(parserPool).release(eq(parser));\n  }\n\n  @Test\n  public void testReturnsNullIfGifDecoderFailsToDecodeFirstFrame() {\n    when(gifHeader.getNumFrames()).thenReturn(1);\n    when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_OK);\n    when(gifDecoder.getNextFrame()).thenReturn(null);\n\n    assertNull(decoder.decode(ByteBuffer.allocate(10), 100, 100, options));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/gif/GifDrawableResourceTest.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class GifDrawableResourceTest {\n  private GifDrawable drawable;\n  private GifDrawableResource resource;\n\n  @Before\n  public void setUp() {\n    drawable = mock(GifDrawable.class);\n    resource = new GifDrawableResource(drawable);\n  }\n\n  @Test\n  public void testReturnsSizeFromDrawable() {\n    final int size = 2134;\n    when(drawable.getSize()).thenReturn(size);\n\n    assertEquals(size, resource.getSize());\n  }\n\n  @Test\n  public void testStopsAndThenRecyclesDrawableWhenRecycled() {\n    resource.recycle();\n\n    InOrder inOrder = inOrder(drawable);\n    inOrder.verify(drawable).stop();\n    inOrder.verify(drawable).recycle();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/gif/GifDrawableTest.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.ArgumentMatchers.isNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.Paint;\nimport android.graphics.PixelFormat;\nimport android.graphics.PorterDuff.Mode;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.TransitionDrawable;\nimport android.os.Build;\nimport android.view.View;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.tests.TearDownGlide;\nimport com.bumptech.glide.tests.Util;\nimport com.bumptech.glide.util.Preconditions;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.shadow.api.Shadow;\nimport org.robolectric.shadows.ShadowCanvas;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class GifDrawableTest {\n  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();\n\n  private GifDrawable drawable;\n  private int frameHeight;\n  private int frameWidth;\n  private Bitmap firstFrame;\n  private int initialSdkVersion;\n\n  @Mock private Drawable.Callback cb;\n  @Mock private GifFrameLoader frameLoader;\n  @Mock private Paint paint;\n  @Mock private Transformation<Bitmap> transformation;\n  private Application context;\n\n  private static Paint isAPaint() {\n    return isA(Paint.class);\n  }\n\n  private static Rect isARect() {\n    return isA(Rect.class);\n  }\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n    frameWidth = 120;\n    frameHeight = 450;\n    firstFrame = Bitmap.createBitmap(frameWidth, frameHeight, Bitmap.Config.RGB_565);\n    drawable = new GifDrawable(frameLoader, paint);\n    when(frameLoader.getWidth()).thenReturn(frameWidth);\n    when(frameLoader.getHeight()).thenReturn(frameHeight);\n    when(frameLoader.getCurrentFrame()).thenReturn(firstFrame);\n    when(frameLoader.getCurrentIndex()).thenReturn(0);\n    drawable.setCallback(cb);\n    initialSdkVersion = Build.VERSION.SDK_INT;\n  }\n\n  @After\n  public void tearDown() {\n    Util.setSdkVersionInt(initialSdkVersion);\n  }\n\n  @Test\n  public void testShouldDrawFirstFrameBeforeAnyFrameRead() {\n    Canvas canvas = new Canvas();\n    drawable.draw(canvas);\n\n    ShadowCanvas shadowCanvas = Shadow.extract(canvas);\n    assertThat(shadowCanvas.getDescription())\n        .isEqualTo(\n            \"Bitmap (\"\n                + firstFrame.getWidth()\n                + \" x \"\n                + firstFrame.getHeight()\n                + \") at (0,0) with height=0 and width=0\");\n  }\n\n  @Test\n  public void testDoesDrawCurrentFrameIfOneIsAvailable() {\n    Canvas canvas = mock(Canvas.class);\n    Bitmap currentFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_4444);\n    when(frameLoader.getCurrentFrame()).thenReturn(currentFrame);\n\n    drawable.draw(canvas);\n    verify(canvas).drawBitmap(eq(currentFrame), (Rect) isNull(), isARect(), isAPaint());\n    verify(canvas, never()).drawBitmap(eq(firstFrame), (Rect) isNull(), isARect(), isAPaint());\n  }\n\n  @Test\n  public void testRequestsNextFrameOnStart() {\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    verify(frameLoader).subscribe(eq(drawable));\n  }\n\n  @Test\n  public void testRequestsNextFrameOnStartWithoutCallToSetVisible() {\n    drawable.start();\n\n    verify(frameLoader).subscribe(eq(drawable));\n  }\n\n  @Test\n  public void testDoesNotRequestNextFrameOnStartIfGotCallToSetVisibleWithVisibleFalse() {\n    drawable.setVisible(false, false);\n    drawable.start();\n\n    verify(frameLoader, never()).subscribe(eq(drawable));\n  }\n\n  @Test\n  public void testDoesNotRequestNextFrameOnStartIfHasSingleFrame() {\n    when(frameLoader.getFrameCount()).thenReturn(1);\n    drawable.setVisible(true, false);\n    drawable.start();\n\n    verify(frameLoader, never()).subscribe(eq(drawable));\n  }\n\n  @Test\n  public void testInvalidatesSelfOnStartIfHasSingleFrame() {\n    when(frameLoader.getFrameCount()).thenReturn(1);\n    drawable.setVisible(true, false);\n    drawable.start();\n\n    verify(cb).invalidateDrawable(eq(drawable));\n  }\n\n  @Test\n  public void testShouldInvalidateSelfOnRun() {\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    verify(cb).invalidateDrawable(eq(drawable));\n  }\n\n  @Test\n  public void testShouldNotScheduleItselfIfAlreadyRunning() {\n    drawable.setVisible(true, true);\n    drawable.start();\n    drawable.start();\n\n    verify(frameLoader, times(1)).subscribe(eq(drawable));\n  }\n\n  @Test\n  public void testReturnsFalseFromIsRunningWhenNotRunning() {\n    assertFalse(drawable.isRunning());\n  }\n\n  @Test\n  public void testReturnsTrueFromIsRunningWhenRunning() {\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    assertTrue(drawable.isRunning());\n  }\n\n  @Test\n  public void testInvalidatesSelfWhenFrameReady() {\n    drawable.setIsRunning(true);\n    drawable.onFrameReady();\n\n    verify(cb).invalidateDrawable(eq(drawable));\n  }\n\n  @Test\n  public void testDoesNotStartLoadingNextFrameWhenCurrentFinishesIfHasNoCallback() {\n    drawable.setIsRunning(true);\n    drawable.setCallback(null);\n    drawable.onFrameReady();\n\n    verify(frameLoader).unsubscribe(eq(drawable));\n  }\n\n  @Test\n  public void testStopsWhenCurrentFrameFinishesIfHasNoCallback() {\n    drawable.setIsRunning(true);\n    drawable.setCallback(null);\n    drawable.onFrameReady();\n\n    assertFalse(drawable.isRunning());\n  }\n\n  @Test\n  public void testUnsubscribesWhenCurrentFinishesIfHasNoCallback() {\n    drawable.setIsRunning(true);\n    drawable.setCallback(null);\n    drawable.onFrameReady();\n\n    verify(frameLoader).unsubscribe(eq(drawable));\n  }\n\n  @Test\n  public void testSetsIsRunningFalseOnStop() {\n    drawable.start();\n    drawable.stop();\n\n    assertFalse(drawable.isRunning());\n  }\n\n  @Test\n  public void testStopsOnSetVisibleFalse() {\n    drawable.start();\n\n    drawable.setVisible(false, true);\n\n    assertFalse(drawable.isRunning());\n  }\n\n  @Test\n  public void testStartsOnSetVisibleTrueIfRunning() {\n    drawable.start();\n    drawable.setVisible(false, false);\n    drawable.setVisible(true, true);\n\n    assertTrue(drawable.isRunning());\n  }\n\n  @Test\n  public void testDoesNotStartOnVisibleTrueIfNotRunning() {\n    drawable.setVisible(true, true);\n\n    assertFalse(drawable.isRunning());\n  }\n\n  @Test\n  public void testDoesNotStartOnSetVisibleIfStartedAndStopped() {\n    drawable.start();\n    drawable.stop();\n    drawable.setVisible(true, true);\n\n    assertFalse(drawable.isRunning());\n  }\n\n  @Test\n  public void testDoesNotImmediatelyRunIfStartedWhileNotVisible() {\n    drawable.setVisible(false, false);\n    drawable.start();\n\n    assertFalse(drawable.isRunning());\n  }\n\n  @Test\n  public void testGetOpacityReturnsTransparent() {\n    assertEquals(PixelFormat.TRANSPARENT, drawable.getOpacity());\n  }\n\n  @Test\n  public void testReturnsFrameCountFromDecoder() {\n    int expected = 4;\n    when(frameLoader.getFrameCount()).thenReturn(expected);\n\n    assertEquals(expected, drawable.getFrameCount());\n  }\n\n  @Test\n  public void testReturnsDefaultFrameIndex() {\n    final int expected = -1;\n\n    when(frameLoader.getCurrentIndex()).thenReturn(expected);\n\n    assertEquals(expected, drawable.getFrameIndex());\n  }\n\n  @Test\n  public void testReturnsNonDefaultFrameIndex() {\n    final int expected = 100;\n\n    when(frameLoader.getCurrentIndex()).thenReturn(expected);\n\n    assertEquals(expected, drawable.getFrameIndex());\n  }\n\n  @Test\n  public void testRecycleCallsClearOnFrameManager() {\n    drawable.recycle();\n\n    verify(frameLoader).clear();\n  }\n\n  @Test\n  public void testIsNotRecycledIfNotRecycled() {\n    assertFalse(drawable.isRecycled());\n  }\n\n  @Test\n  public void testIsRecycledAfterRecycled() {\n    drawable.recycle();\n\n    assertTrue(drawable.isRecycled());\n  }\n\n  @Test\n  public void testReturnsNonNullConstantState() {\n    assertNotNull(drawable.getConstantState());\n  }\n\n  @Test\n  public void testReturnsSizeFromFrameLoader() {\n    int size = 1243;\n    when(frameLoader.getSize()).thenReturn(size);\n\n    assertThat(drawable.getSize()).isEqualTo(size);\n  }\n\n  @Test\n  public void testReturnsNewDrawableFromConstantState() {\n    Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    drawable =\n        new GifDrawable(\n            ApplicationProvider.getApplicationContext(),\n            mock(GifDecoder.class),\n            transformation,\n            100,\n            100,\n            firstFrame);\n\n    assertNotNull(Preconditions.checkNotNull(drawable.getConstantState()).newDrawable());\n    assertNotNull(\n        drawable\n            .getConstantState()\n            .newDrawable(ApplicationProvider.getApplicationContext().getResources()));\n  }\n\n  @Test\n  public void testReturnsFrameWidthAndHeightForIntrinsicDimensions() {\n    assertEquals(frameWidth, drawable.getIntrinsicWidth());\n    assertEquals(frameHeight, drawable.getIntrinsicHeight());\n  }\n\n  @Test\n  public void testLoopsASingleTimeIfLoopCountIsSetToOne() {\n    final int loopCount = 1;\n    final int frameCount = 2;\n    when(frameLoader.getFrameCount()).thenReturn(frameCount);\n    drawable.setLoopCount(loopCount);\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    runLoops(loopCount, frameCount);\n\n    verifyRanLoops(loopCount, frameCount);\n    assertFalse(\"drawable should be stopped after loop is completed\", drawable.isRunning());\n  }\n\n  @Test\n  public void testLoopsForeverIfLoopCountIsSetToLoopForever() {\n    final int loopCount = 40;\n    final int frameCount = 2;\n\n    when(frameLoader.getFrameCount()).thenReturn(frameCount);\n    drawable.setLoopCount(GifDrawable.LOOP_FOREVER);\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    runLoops(loopCount, frameCount);\n\n    verifyRanLoops(loopCount, frameCount);\n    assertTrue(\"drawable should be still running\", drawable.isRunning());\n  }\n\n  @Test\n  public void testLoopsOnceIfLoopCountIsSetToOneWithThreeFrames() {\n    final int loopCount = 1;\n    final int frameCount = 3;\n\n    when(frameLoader.getFrameCount()).thenReturn(frameCount);\n    drawable.setLoopCount(loopCount);\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    runLoops(loopCount, frameCount);\n\n    verifyRanLoops(loopCount, frameCount);\n    assertFalse(\"drawable should be stopped after loop is completed\", drawable.isRunning());\n  }\n\n  @Test\n  public void testLoopsThreeTimesIfLoopCountIsSetToThree() {\n    final int loopCount = 3;\n    final int frameCount = 2;\n\n    when(frameLoader.getFrameCount()).thenReturn(frameCount);\n    drawable.setLoopCount(loopCount);\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    runLoops(loopCount, frameCount);\n\n    verifyRanLoops(loopCount, frameCount);\n    assertFalse(\"drawable should be stopped after loop is completed\", drawable.isRunning());\n  }\n\n  @Test\n  public void testCallingStartResetsLoopCounter() {\n    when(frameLoader.getFrameCount()).thenReturn(2);\n    drawable.setLoopCount(1);\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    drawable.onFrameReady();\n    when(frameLoader.getCurrentIndex()).thenReturn(1);\n    drawable.onFrameReady();\n    assertFalse(\"drawable should be stopped after loop is completed\", drawable.isRunning());\n\n    drawable.start();\n\n    when(frameLoader.getCurrentIndex()).thenReturn(0);\n    drawable.onFrameReady();\n    when(frameLoader.getCurrentIndex()).thenReturn(1);\n    drawable.onFrameReady();\n\n    // 4 onFrameReady(), 2 start()\n    verify(cb, times(4 + 2)).invalidateDrawable(eq(drawable));\n    assertFalse(\"drawable should be stopped after loop is completed\", drawable.isRunning());\n  }\n\n  @Test\n  public void testChangingTheLoopCountAfterHittingTheMaxLoopCount() {\n    final int initialLoopCount = 1;\n    final int frameCount = 2;\n\n    when(frameLoader.getFrameCount()).thenReturn(frameCount);\n    drawable.setLoopCount(initialLoopCount);\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    runLoops(initialLoopCount, frameCount);\n    assertFalse(\"drawable should be stopped after loop is completed\", drawable.isRunning());\n\n    final int newLoopCount = 2;\n\n    drawable.setLoopCount(newLoopCount);\n    drawable.start();\n\n    runLoops(newLoopCount, frameCount);\n\n    int numStarts = 2;\n    int expectedFrames = (initialLoopCount + newLoopCount) * frameCount + numStarts;\n    verify(cb, times(expectedFrames)).invalidateDrawable(eq(drawable));\n    assertFalse(\"drawable should be stopped after loop is completed\", drawable.isRunning());\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfGivenLoopCountLessThanZeroAndNotInfinite() {\n    drawable.setLoopCount(-2);\n  }\n\n  @Test\n  public void testUsesDecoderTotalLoopCountIfLoopCountIsLoopIntrinsic() {\n    final int frameCount = 3;\n    final int loopCount = 2;\n    when(frameLoader.getLoopCount()).thenReturn(loopCount);\n    when(frameLoader.getFrameCount()).thenReturn(frameCount);\n    drawable.setLoopCount(GifDrawable.LOOP_INTRINSIC);\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    runLoops(loopCount, frameCount);\n\n    verifyRanLoops(loopCount, frameCount);\n    assertFalse(\"drawable should be stopped after loop is completed\", drawable.isRunning());\n  }\n\n  @Test\n  public void testLoopsForeverIfLoopCountIsLoopIntrinsicAndTotalIterationCountIsForever() {\n    final int frameCount = 3;\n    final int loopCount = 40;\n    when(frameLoader.getLoopCount()).thenReturn(GifDecoder.TOTAL_ITERATION_COUNT_FOREVER);\n    when(frameLoader.getFrameCount()).thenReturn(frameCount);\n    drawable.setLoopCount(GifDrawable.LOOP_INTRINSIC);\n    drawable.setVisible(true, true);\n    drawable.start();\n\n    runLoops(loopCount, frameCount);\n\n    verifyRanLoops(loopCount, frameCount);\n    assertTrue(\"drawable should be still running\", drawable.isRunning());\n  }\n\n  @Test\n  public void testDoesNotDrawFrameAfterRecycle() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 112341, Bitmap.Config.RGB_565);\n    drawable.setVisible(true, true);\n    drawable.start();\n    when(frameLoader.getCurrentFrame()).thenReturn(bitmap);\n    drawable.onFrameReady();\n    drawable.recycle();\n    Canvas canvas = mock(Canvas.class);\n    drawable.draw(canvas);\n    verify(canvas, never()).drawBitmap(eq(bitmap), isARect(), isARect(), isAPaint());\n  }\n\n  @Test\n  public void testSetsFrameTransformationOnFrameManager() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    drawable.setFrameTransformation(transformation, bitmap);\n\n    verify(frameLoader).setFrameTransformation(eq(transformation), eq(bitmap));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfConstructedWithNullFirstFrame() {\n    new GifDrawable(\n        ApplicationProvider.getApplicationContext(),\n        mock(GifDecoder.class),\n        transformation,\n        100,\n        100,\n        null);\n  }\n\n  @Test\n  public void testAppliesGravityOnDrawAfterBoundsChange() {\n    Rect bounds = new Rect(0, 0, frameWidth * 2, frameHeight * 2);\n    drawable.setBounds(bounds);\n\n    Canvas canvas = mock(Canvas.class);\n    drawable.draw(canvas);\n\n    verify(canvas).drawBitmap(isA(Bitmap.class), (Rect) isNull(), eq(bounds), eq(paint));\n  }\n\n  @Test\n  public void testSetAlphaSetsAlphaOnPaint() {\n    int alpha = 100;\n    drawable.setAlpha(alpha);\n    verify(paint).setAlpha(eq(alpha));\n  }\n\n  @Test\n  public void testSetColorFilterSetsColorFilterOnPaint() {\n    ColorFilter colorFilter = new PorterDuffColorFilter(Color.RED, Mode.ADD);\n    drawable.setColorFilter(colorFilter);\n\n    // Use ArgumentCaptor instead of eq() due to b/73121412 where ShadowPorterDuffColorFilter.equals\n    // uses a method that can't be found (PorterDuffColorFilter.getColor).\n    ArgumentCaptor<ColorFilter> captor = ArgumentCaptor.forClass(ColorFilter.class);\n    verify(paint).setColorFilter(captor.capture());\n    assertThat(captor.getValue()).isSameInstanceAs(colorFilter);\n  }\n\n  @Test\n  public void testReturnsCurrentTransformationInGetFrameTransformation() {\n    @SuppressWarnings(\"unchecked\")\n    Transformation<Bitmap> newTransformation = mock(Transformation.class);\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    drawable.setFrameTransformation(newTransformation, bitmap);\n\n    verify(frameLoader).setFrameTransformation(eq(newTransformation), eq(bitmap));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfCreatedWithNullState() {\n    new GifDrawable(null);\n  }\n\n  @Test\n  public void onFrameReady_whenAttachedToDrawableCallbackButNotViewCallback_stops() {\n    TransitionDrawable topLevel = new TransitionDrawable(new Drawable[] {drawable});\n    drawable.setCallback(topLevel);\n    topLevel.setCallback(null);\n\n    drawable.start();\n    drawable.onFrameReady();\n\n    assertThat(drawable.isRunning()).isFalse();\n  }\n\n  @Test\n  public void onFrameReady_whenAttachedtoDrawableCallbackWithViewCallbackParent_doesNotStop() {\n    TransitionDrawable topLevel = new TransitionDrawable(new Drawable[] {drawable});\n    drawable.setCallback(topLevel);\n    topLevel.setCallback(new View(context));\n\n    drawable.start();\n    drawable.onFrameReady();\n\n    assertThat(drawable.isRunning()).isTrue();\n  }\n\n  private void verifyRanLoops(int loopCount, int frameCount) {\n    // 1 for invalidate in start().\n    verify(cb, times(1 + loopCount * frameCount)).invalidateDrawable(eq(drawable));\n  }\n\n  private void runLoops(int loopCount, int frameCount) {\n    for (int loop = 0; loop < loopCount; loop++) {\n      for (int frame = 0; frame < frameCount; frame++) {\n        when(frameLoader.getCurrentIndex()).thenReturn(frame);\n        assertTrue(\n            \"drawable should be started before calling drawable.onFrameReady()\",\n            drawable.isRunning());\n        drawable.onFrameReady();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformationTest.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.load.resource.UnitTransformation;\nimport com.bumptech.glide.tests.KeyTester;\nimport com.bumptech.glide.tests.Util;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class GifDrawableTransformationTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n  @Mock private Transformation<Bitmap> wrapped;\n  @Mock private BitmapPool bitmapPool;\n\n  private GifDrawableTransformation transformation;\n  private Context context;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    context = ApplicationProvider.getApplicationContext();\n\n    Glide.init(context, new GlideBuilder().setBitmapPool(bitmapPool));\n    transformation = new GifDrawableTransformation(wrapped);\n  }\n\n  @After\n  public void tearDown() {\n    Glide.tearDown();\n  }\n\n  @Test\n  @SuppressWarnings(\"unchecked\")\n  public void testSetsTransformationAsFrameTransformation() {\n    Resource<GifDrawable> resource = mockResource();\n    GifDrawable gifDrawable = mock(GifDrawable.class);\n    Transformation<Bitmap> unitTransformation = UnitTransformation.get();\n    when(gifDrawable.getFrameTransformation()).thenReturn(unitTransformation);\n    when(gifDrawable.getIntrinsicWidth()).thenReturn(500);\n    when(gifDrawable.getIntrinsicHeight()).thenReturn(500);\n    when(resource.get()).thenReturn(gifDrawable);\n\n    Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(gifDrawable.getFirstFrame()).thenReturn(firstFrame);\n\n    final int width = 123;\n    final int height = 456;\n    Bitmap expectedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n    Resource<Bitmap> expectedResource = mockResource();\n    when(expectedResource.get()).thenReturn(expectedBitmap);\n    when(wrapped.transform(any(Context.class), Util.<Bitmap>anyResource(), anyInt(), anyInt()))\n        .thenReturn(expectedResource);\n\n    transformation.transform(context, resource, width, height);\n\n    verify(gifDrawable).setFrameTransformation(isA(Transformation.class), eq(expectedBitmap));\n  }\n\n  @Test\n  public void testEquals() throws NoSuchAlgorithmException {\n    doAnswer(new Util.WriteDigest(\"first\"))\n        .when(wrapped)\n        .updateDiskCacheKey(isA(MessageDigest.class));\n    @SuppressWarnings(\"unchecked\")\n    Transformation<Bitmap> other = mock(Transformation.class);\n    doAnswer(new Util.WriteDigest(\"other\"))\n        .when(other)\n        .updateDiskCacheKey(isA(MessageDigest.class));\n    keyTester\n        .addEquivalenceGroup(\n            transformation,\n            new GifDrawableTransformation(wrapped),\n            new GifDrawableTransformation(wrapped))\n        .addEquivalenceGroup(wrapped)\n        .addEquivalenceGroup(new GifDrawableTransformation(other))\n        .addRegressionTest(\n            new GifDrawableTransformation(wrapped),\n            \"a7937b64b8caa58f03721bb6bacf5c78cb235febe0e70b1b84cd99541461a08e\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/gif/GifFrameLoaderTest.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.robolectric.annotation.LooperMode.Mode.LEGACY;\n\nimport android.graphics.Bitmap;\nimport android.os.Handler;\nimport android.os.Message;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.resource.gif.GifFrameLoader.DelayTarget;\nimport com.bumptech.glide.load.resource.gif.GifFrameLoader.FrameCallback;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.tests.TearDownGlide;\nimport com.bumptech.glide.tests.Util.ReturnsSelfAnswer;\nimport com.bumptech.glide.util.Util;\nimport java.nio.ByteBuffer;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.LooperMode;\n\n@LooperMode(LEGACY)\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class GifFrameLoaderTest {\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n\n  @Mock private GifFrameLoader.FrameCallback callback;\n  @Mock private GifDecoder gifDecoder;\n  @Mock private Handler handler;\n  @Mock private Transformation<Bitmap> transformation;\n  @Mock private RequestManager requestManager;\n  private GifFrameLoader loader;\n  private RequestBuilder<Bitmap> requestBuilder;\n  private Bitmap firstFrame;\n\n  @SuppressWarnings(\"unchecked\")\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    when(handler.obtainMessage(anyInt(), isA(DelayTarget.class))).thenReturn(mock(Message.class));\n\n    firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n\n    ByteBuffer byteBuffer = ByteBuffer.allocate(10);\n    when(gifDecoder.getData()).thenReturn(byteBuffer);\n\n    requestBuilder = mock(RequestBuilder.class, new ReturnsSelfAnswer());\n\n    loader = createGifFrameLoader(handler);\n  }\n\n  @NonNull\n  private GifFrameLoader createGifFrameLoader(Handler handler) {\n    Glide glide = getGlideSingleton();\n    GifFrameLoader result =\n        new GifFrameLoader(\n            glide.getBitmapPool(),\n            requestManager,\n            gifDecoder,\n            handler,\n            requestBuilder,\n            transformation,\n            firstFrame);\n    result.subscribe(callback);\n    return result;\n  }\n\n  private static Glide getGlideSingleton() {\n    return Glide.get(ApplicationProvider.getApplicationContext());\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Test\n  public void testSetFrameTransformationSetsTransformationOnRequestBuilder() {\n    verify(requestBuilder, times(2)).apply(isA(RequestOptions.class));\n    Transformation<Bitmap> transformation = mock(Transformation.class);\n    loader.setFrameTransformation(transformation, firstFrame);\n\n    verify(requestBuilder, times(3)).apply(isA(RequestOptions.class));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testSetFrameTransformationThrowsIfGivenNullTransformation() {\n    loader.setFrameTransformation(null, null);\n  }\n\n  @Test\n  public void testReturnsSizeFromGifDecoderAndCurrentFrame() {\n    int decoderByteSize = 123456;\n    when(gifDecoder.getByteSize()).thenReturn(decoderByteSize);\n    assertThat(loader.getSize()).isEqualTo(decoderByteSize + Util.getBitmapByteSize(firstFrame));\n  }\n\n  @Test\n  public void testStartGetsNextFrameIfNotStartedAndWithNoLoadPending() {\n    verify(requestBuilder).into(aTarget());\n  }\n\n  @Test\n  public void testGetNextFrameIncrementsSignatureAndAdvancesDecoderBeforeStartingLoad() {\n    InOrder order = inOrder(gifDecoder, requestBuilder);\n    order.verify(gifDecoder).advance();\n    order.verify(requestBuilder).apply(isA(RequestOptions.class));\n    order.verify(requestBuilder).into(aTarget());\n  }\n\n  @Test\n  public void testGetCurrentFrameReturnsFirstFrameWHenNoLoadHasCompleted() {\n    assertThat(loader.getCurrentFrame()).isEqualTo(firstFrame);\n  }\n\n  @Test\n  public void testGetCurrentFrameReturnsCurrentBitmapAfterLoadHasCompleted() {\n    final Bitmap result = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);\n    DelayTarget target = mock(DelayTarget.class);\n    when(target.getResource()).thenReturn(result);\n    loader.onFrameReady(target);\n\n    assertEquals(result, loader.getCurrentFrame());\n  }\n\n  @Test\n  public void testStartDoesNotStartIfAlreadyRunning() {\n    loader.subscribe(mock(FrameCallback.class));\n\n    verify(requestBuilder, times(1)).into(aTarget());\n  }\n\n  @Test\n  public void testGetNextFrameDoesNotStartLoadIfLoaderIsNotRunning() {\n    verify(requestBuilder, times(1)).into(aTarget());\n    loader.unsubscribe(callback);\n    loader.onFrameReady(mock(DelayTarget.class));\n\n    verify(requestBuilder, times(1)).into(aTarget());\n  }\n\n  @Test\n  public void testGetNextFrameDoesNotStartLoadIfLoadIsInProgress() {\n    loader.unsubscribe(callback);\n    loader.subscribe(callback);\n\n    verify(requestBuilder, times(1)).into(aTarget());\n  }\n\n  @Test\n  public void testGetNextFrameDoesStartLoadIfRestartedAndNoLoadIsInProgress() {\n    loader.unsubscribe(callback);\n\n    loader.onFrameReady(mock(DelayTarget.class));\n    loader.subscribe(callback);\n\n    verify(requestBuilder, times(2)).into(aTarget());\n  }\n\n  @Test\n  public void testGetNextFrameDoesStartLoadAfterLoadCompletesIfStarted() {\n    loader.onFrameReady(mock(DelayTarget.class));\n\n    verify(requestBuilder, times(2)).into(aTarget());\n  }\n\n  @Test\n  public void testOnFrameReadyClearsPreviousFrame() {\n    // Force the loader to create a real Handler.\n    loader = createGifFrameLoader(null);\n\n    DelayTarget previous = newDelayTarget();\n    Request previousRequest = mock(Request.class);\n    previous.setRequest(previousRequest);\n    previous.onResourceReady(\n        Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888), /* transition= */ null);\n\n    DelayTarget current = mock(DelayTarget.class);\n    when(current.getResource()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565));\n    loader.onFrameReady(previous);\n    loader.onFrameReady(current);\n\n    verify(requestManager).clear(eq(previous));\n  }\n\n  @Test\n  public void testOnFrameReadyWithNullResourceDoesNotClearPreviousFrame() {\n    // Force the loader to create a real Handler by passing null.\n    loader = createGifFrameLoader(null);\n\n    DelayTarget previous = newDelayTarget();\n    Request previousRequest = mock(Request.class);\n    previous.setRequest(previousRequest);\n    previous.onResourceReady(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888), null);\n\n    DelayTarget current = mock(DelayTarget.class);\n    when(current.getResource()).thenReturn(null);\n    loader.onFrameReady(previous);\n    loader.onFrameReady(current);\n\n    verify(previousRequest, never()).clear();\n  }\n\n  @Test\n  public void testDelayTargetSendsMessageWithHandlerDelayed() {\n    long targetTime = 1234;\n    DelayTarget delayTarget = new DelayTarget(handler, 1, targetTime);\n    delayTarget.onResourceReady(\n        Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888), null\n        /*glideAnimation*/ );\n    verify(handler).sendMessageAtTime(isA(Message.class), eq(targetTime));\n  }\n\n  @Test\n  public void testDelayTargetSetsResourceOnResourceReady() {\n    DelayTarget delayTarget = new DelayTarget(handler, 1, 1);\n    Bitmap expected = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);\n    delayTarget.onResourceReady(expected, null /*glideAnimation*/);\n\n    assertEquals(expected, delayTarget.getResource());\n  }\n\n  @Test\n  public void testClearsCompletedLoadOnFrameReadyIfCleared() {\n    // Force the loader to create a real Handler by passing null;\n    loader = createGifFrameLoader(null);\n    loader.clear();\n    DelayTarget delayTarget = newDelayTarget();\n    Request request = mock(Request.class);\n    delayTarget.setRequest(request);\n\n    loader.onFrameReady(delayTarget);\n\n    verify(requestManager).clear(eq(delayTarget));\n  }\n\n  @Test\n  public void\n      testDoesNotReturnResourceForCompletedFrameInGetCurrentFrameIfLoadCompletesWhileCleared() {\n    loader.clear();\n    DelayTarget delayTarget = mock(DelayTarget.class);\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    when(delayTarget.getResource()).thenReturn(bitmap);\n\n    loader.onFrameReady(delayTarget);\n\n    assertNull(loader.getCurrentFrame());\n  }\n\n  @Test\n  public void onFrameReady_whenNotRunning_doesNotClearPreviouslyLoadedImage() {\n    loader = createGifFrameLoader(/* handler= */ null);\n    DelayTarget loaded = mock(DelayTarget.class);\n    when(loaded.getResource()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(loaded);\n    loader.unsubscribe(callback);\n\n    DelayTarget nextFrame = mock(DelayTarget.class);\n    when(nextFrame.getResource())\n        .thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(nextFrame);\n    verify(requestManager, never()).clear(loaded);\n  }\n\n  @Test\n  public void onFrameReady_whenNotRunning_clearsPendingFrameOnClear() {\n    loader = createGifFrameLoader(/* handler= */ null);\n    DelayTarget loaded = mock(DelayTarget.class);\n    when(loaded.getResource()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(loaded);\n    loader.unsubscribe(callback);\n\n    DelayTarget nextFrame = mock(DelayTarget.class);\n    when(nextFrame.getResource())\n        .thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(nextFrame);\n\n    loader.clear();\n    verify(requestManager).clear(loaded);\n    verify(requestManager).clear(nextFrame);\n  }\n\n  @Test\n  public void onFrameReady_whenNotRunning_clearsOldFrameOnStart() {\n    loader = createGifFrameLoader(/* handler= */ null);\n    DelayTarget loaded = mock(DelayTarget.class);\n    when(loaded.getResource()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(loaded);\n    loader.unsubscribe(callback);\n\n    DelayTarget nextFrame = mock(DelayTarget.class);\n    when(nextFrame.getResource())\n        .thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(nextFrame);\n\n    loader.subscribe(callback);\n    verify(requestManager).clear(loaded);\n  }\n\n  @Test\n  public void onFrameReady_whenNotRunning_callsFrameReadyWithNewFrameOnStart() {\n    loader = createGifFrameLoader(/* handler= */ null);\n    DelayTarget loaded = mock(DelayTarget.class);\n    when(loaded.getResource()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(loaded);\n    loader.unsubscribe(callback);\n\n    DelayTarget nextFrame = mock(DelayTarget.class);\n    Bitmap expected = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);\n    when(nextFrame.getResource()).thenReturn(expected);\n    loader.onFrameReady(nextFrame);\n\n    verify(callback, times(1)).onFrameReady();\n    loader.subscribe(callback);\n    verify(callback, times(2)).onFrameReady();\n    assertThat(loader.getCurrentFrame()).isEqualTo(expected);\n  }\n\n  @Test\n  public void onFrameReady_whenInvisible_setVisibleLater() {\n    loader = createGifFrameLoader(/* handler= */ null);\n    // The target is invisible at this point.\n    loader.unsubscribe(callback);\n    loader.setNextStartFromFirstFrame();\n    DelayTarget loaded = mock(DelayTarget.class);\n    when(loaded.getResource()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(loaded);\n    loader.subscribe(callback);\n  }\n\n  @Test\n  public void startFromFirstFrame_withPendingFrame_clearsPendingFrame() {\n    loader = createGifFrameLoader(/* handler= */ null);\n    DelayTarget loaded = mock(DelayTarget.class);\n    when(loaded.getResource()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));\n    loader.onFrameReady(loaded);\n    loader.unsubscribe(callback);\n\n    DelayTarget nextFrame = mock(DelayTarget.class);\n    Bitmap expected = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);\n    when(nextFrame.getResource()).thenReturn(expected);\n    loader.onFrameReady(nextFrame);\n\n    loader.setNextStartFromFirstFrame();\n    verify(requestManager).clear(nextFrame);\n\n    loader.subscribe(callback);\n    verify(callback, times(1)).onFrameReady();\n  }\n\n  private DelayTarget newDelayTarget() {\n    return new DelayTarget(handler, /* index= */ 0, /* targetTime= */ 0);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static Target<Bitmap> aTarget() {\n    return isA(Target.class);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/gif/GifFrameResourceDecoderTest.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport com.bumptech.glide.gifdecoder.GifDecoder;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.IOException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class GifFrameResourceDecoderTest {\n  private GifDecoder gifDecoder;\n  private GifFrameResourceDecoder resourceDecoder;\n  private Options options;\n\n  @Before\n  public void setUp() {\n    gifDecoder = mock(GifDecoder.class);\n    resourceDecoder = new GifFrameResourceDecoder(mock(BitmapPool.class));\n    options = new Options();\n  }\n\n  @Test\n  public void testReturnsFrameFromGifDecoder() throws IOException {\n    Bitmap expected = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_4444);\n    when(gifDecoder.getNextFrame()).thenReturn(expected);\n\n    assertEquals(\n        expected,\n        Preconditions.checkNotNull(resourceDecoder.decode(gifDecoder, 100, 100, options)).get());\n  }\n\n  @Test\n  public void testReturnsNullIfGifDecoderReturnsNullFrame() {\n    when(gifDecoder.getNextFrame()).thenReturn(null);\n\n    assertNull(resourceDecoder.decode(gifDecoder, 100, 100, options));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/gif/StreamGifDecoderTest.java",
    "content": "package com.bumptech.glide.load.resource.gif;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.bumptech.glide.load.ImageHeaderParser;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class StreamGifDecoderTest {\n  private static final byte[] GIF_HEADER = new byte[] {0x47, 0x49, 0x46};\n\n  @Mock private ResourceDecoder<ByteBuffer, GifDrawable> byteBufferDecoder;\n  private StreamGifDecoder decoder;\n  private Options options;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    List<ImageHeaderParser> parsers = new ArrayList<>();\n    parsers.add(new DefaultImageHeaderParser());\n\n    decoder = new StreamGifDecoder(parsers, byteBufferDecoder, new LruArrayPool());\n    options = new Options();\n  }\n\n  @Test\n  public void testDoesNotHandleStreamIfEnabledButNotAGif() throws IOException {\n    assertThat(decoder.handles(new ByteArrayInputStream(new byte[0]), options)).isFalse();\n  }\n\n  @Test\n  public void testHandlesStreamIfContainsGifHeaderAndDisabledIsNotSet() throws IOException {\n    assertThat(decoder.handles(new ByteArrayInputStream(GIF_HEADER), options)).isTrue();\n  }\n\n  @Test\n  public void testHandlesStreamIfContainsGifHeaderAndDisabledIsFalse() throws IOException {\n    options.set(GifOptions.DISABLE_ANIMATION, false);\n    assertThat(decoder.handles(new ByteArrayInputStream(GIF_HEADER), options)).isTrue();\n  }\n\n  @Test\n  public void testDoesNotHandleStreamIfDisabled() throws IOException {\n    options.set(GifOptions.DISABLE_ANIMATION, true);\n    assertThat(decoder.handles(new ByteArrayInputStream(GIF_HEADER), options)).isFalse();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/transcode/BitmapBytesTranscoderTest.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.ByteArrayOutputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapBytesTranscoderTest {\n  private BitmapBytesTranscoderHarness harness;\n\n  @Before()\n  public void setUp() {\n    harness = new BitmapBytesTranscoderHarness();\n  }\n\n  @Test\n  public void testReturnsBytesOfGivenBitmap() {\n    assertThat(harness.getTranscodeResult()).isEqualTo(harness.getExpectedData());\n  }\n\n  @Test\n  public void testUsesGivenQuality() {\n    harness.quality = 66;\n    assertThat(harness.getTranscodeResult()).isEqualTo(harness.getExpectedData());\n  }\n\n  @Test\n  public void testUsesGivenFormat() {\n    for (Bitmap.CompressFormat format : Bitmap.CompressFormat.values()) {\n      harness.compressFormat = format;\n      assertThat(harness.getTranscodeResult()).isEqualTo(harness.getExpectedData());\n    }\n  }\n\n  @Test\n  public void testBitmapResourceIsRecycled() {\n    harness.getTranscodeResult();\n\n    verify(harness.bitmapResource).recycle();\n  }\n\n  private static class BitmapBytesTranscoderHarness {\n    Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;\n    int quality = 100;\n    final Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8);\n    final Resource<Bitmap> bitmapResource = mockResource();\n    final Options options = new Options();\n\n    BitmapBytesTranscoderHarness() {\n      when(bitmapResource.get()).thenReturn(bitmap);\n    }\n\n    byte[] getTranscodeResult() {\n      BitmapBytesTranscoder transcoder = new BitmapBytesTranscoder(compressFormat, quality);\n      Resource<byte[]> bytesResource =\n          Preconditions.checkNotNull(transcoder.transcode(bitmapResource, options));\n\n      return bytesResource.get();\n    }\n\n    byte[] getExpectedData() {\n      ByteArrayOutputStream os = new ByteArrayOutputStream();\n      bitmap.compress(compressFormat, quality, os);\n      return os.toByteArray();\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/transcode/BitmapDrawableTranscoderTest.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapDrawableTranscoderTest {\n  private BitmapDrawableTranscoder transcoder;\n\n  @Before\n  public void setUp() {\n    transcoder =\n        new BitmapDrawableTranscoder(ApplicationProvider.getApplicationContext().getResources());\n  }\n\n  @Test\n  public void testReturnsBitmapDrawableResourceContainingGivenBitmap() {\n    Bitmap expected = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    Resource<Bitmap> resource = mockResource();\n    when(resource.get()).thenReturn(expected);\n\n    Resource<BitmapDrawable> transcoded = transcoder.transcode(resource, new Options());\n\n    assertEquals(expected, transcoded.get().getBitmap());\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/transcode/GifDrawableBytesTranscoderTest.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.junit.Assert.assertArrayEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.Charset;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class GifDrawableBytesTranscoderTest {\n  private GifDrawableBytesTranscoder transcoder;\n  private GifDrawable gifDrawable;\n  private Resource<GifDrawable> resource;\n\n  @Before\n  public void setUp() {\n    gifDrawable = mock(GifDrawable.class);\n    resource = mockResource();\n    when(resource.get()).thenReturn(gifDrawable);\n    transcoder = new GifDrawableBytesTranscoder();\n  }\n\n  @Test\n  public void testReturnsBytesOfGivenGifDrawable() {\n    for (String fakeData : new String[] {\"test\", \"1235asfklaw3\", \"@$@#\"}) {\n      ByteBuffer expected = ByteBuffer.wrap(fakeData.getBytes(Charset.defaultCharset()));\n      when(gifDrawable.getBuffer()).thenReturn(expected);\n\n      Resource<byte[]> transcoded = transcoder.transcode(resource, new Options());\n\n      assertArrayEquals(expected.array(), transcoded.get());\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/transcode/TranscoderRegistryTest.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\n\nimport java.io.File;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class TranscoderRegistryTest {\n  private TranscoderRegistry factories;\n\n  @Before\n  public void setUp() {\n    factories = new TranscoderRegistry();\n  }\n\n  @Test\n  public void testReturnsUnitDecoderIfClassesAreIdentical() {\n    assertEquals(UnitTranscoder.get(), factories.get(Object.class, Object.class));\n  }\n\n  @Test\n  public void testCanRegisterAndRetrieveResourceTranscoder() {\n    @SuppressWarnings(\"unchecked\")\n    ResourceTranscoder<File, String> transcoder = mock(ResourceTranscoder.class);\n    factories.register(File.class, String.class, transcoder);\n\n    assertEquals(transcoder, factories.get(File.class, String.class));\n  }\n\n  @Test\n  public void testDoesNotThrowIfRequestCanBeSatisfiedByUnitTranscoder() {\n    // Assignable from.\n    assertNotNull(factories.get(Integer.class, Number.class));\n    // Equal to.\n    assertNotNull(factories.get(Integer.class, Integer.class));\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfNoTranscoderRegistered() {\n    factories.get(File.class, Integer.class);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/load/resource/transcode/UnitTranscoderTest.java",
    "content": "package com.bumptech.glide.load.resource.transcode;\n\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static org.junit.Assert.assertEquals;\n\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class UnitTranscoderTest {\n\n  @Test\n  public void testReturnsTheGivenResource() {\n    Resource<Object> resource = mockResource();\n    ResourceTranscoder<Object, Object> unitTranscoder = UnitTranscoder.get();\n\n    assertEquals(resource, unitTranscoder.transcode(resource, new Options()));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/manager/DefaultConnectivityMonitorFactoryTest.java",
    "content": "package com.bumptech.glide.manager;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.robolectric.Shadows.shadowOf;\n\nimport android.app.Application;\nimport androidx.test.core.app.ApplicationProvider;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class DefaultConnectivityMonitorFactoryTest {\n  private ConnectivityMonitorFactory factory;\n\n  @Before\n  public void setUp() {\n    factory = new DefaultConnectivityMonitorFactory();\n  }\n\n  @Test\n  public void testReturnsDefaultConnectivityMonitorWhenHasPermission() {\n    shadowOf((Application) ApplicationProvider.getApplicationContext())\n        .grantPermissions(\"android.permission.ACCESS_NETWORK_STATE\");\n    ConnectivityMonitor connectivityMonitor =\n        factory.build(\n            ApplicationProvider.getApplicationContext(),\n            mock(ConnectivityMonitor.ConnectivityListener.class));\n    assertThat(connectivityMonitor).isInstanceOf(DefaultConnectivityMonitor.class);\n  }\n\n  @Test\n  public void testReturnsNullConnectivityMonitorWhenDoesNotHavePermission() {\n    ConnectivityMonitor connectivityMonitor =\n        factory.build(\n            ApplicationProvider.getApplicationContext(),\n            mock(ConnectivityMonitor.ConnectivityListener.class));\n    assertThat(connectivityMonitor).isInstanceOf(NullConnectivityMonitor.class);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/manager/DefaultConnectivityMonitorTest.java",
    "content": "package com.bumptech.glide.manager;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.robolectric.Shadows.shadowOf;\nimport static org.robolectric.annotation.LooperMode.Mode.LEGACY;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.ConnectivityManager;\nimport android.net.ConnectivityManager.NetworkCallback;\nimport android.net.Network;\nimport android.net.NetworkInfo;\nimport android.os.Build;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.manager.DefaultConnectivityMonitorTest.PermissionConnectivityManager;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\nimport org.robolectric.annotation.LooperMode;\nimport org.robolectric.shadow.api.Shadow;\nimport org.robolectric.shadows.ShadowConnectivityManager;\nimport org.robolectric.shadows.ShadowNetwork;\nimport org.robolectric.shadows.ShadowNetworkInfo;\n\n@LooperMode(LEGACY)\n@RunWith(RobolectricTestRunner.class)\n@Config(\n    sdk = {24},\n    shadows = PermissionConnectivityManager.class)\npublic class DefaultConnectivityMonitorTest {\n  @Mock private ConnectivityMonitor.ConnectivityListener listener;\n  private DefaultConnectivityMonitor monitor;\n  private ConnectivityHarness harness;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    monitor = new DefaultConnectivityMonitor(ApplicationProvider.getApplicationContext(), listener);\n    harness =\n        Build.VERSION.SDK_INT >= Build.VERSION_CODES.N\n            ? new ConnectivityHarnessPost24()\n            : new ConnectivityHarnessPre24();\n  }\n\n  @After\n  public void tearDown() {\n    SingletonConnectivityReceiver.reset();\n  }\n\n  @Test\n  public void testRegistersReceiverOnStart() {\n    monitor.onStart();\n\n    assertThat(harness.getRegisteredReceivers()).isEqualTo(1);\n  }\n\n  @Test\n  public void testDoesNotRegisterTwiceOnStart() {\n    monitor.onStart();\n    monitor.onStart();\n\n    assertThat(harness.getRegisteredReceivers()).isEqualTo(1);\n  }\n\n  @Test\n  public void testUnregistersReceiverOnStop() {\n    monitor.onStart();\n    monitor.onStop();\n\n    assertThat(harness.getRegisteredReceivers()).isEqualTo(0);\n  }\n\n  @Test\n  public void testHandlesUnregisteringTwiceInARow() {\n    monitor.onStop();\n    monitor.onStop();\n\n    assertThat(harness.getRegisteredReceivers()).isEqualTo(0);\n  }\n\n  @Test\n  public void testDoesNotNotifyListenerIfConnectedAndBecomesConnected() {\n    harness.connect();\n\n    monitor.onStart();\n    harness.broadcast();\n\n    verify(listener, never()).onConnectivityChanged(anyBoolean());\n  }\n\n  @Test\n  public void testNotifiesListenerIfConnectedAndBecomesDisconnected() {\n    harness.connect();\n\n    monitor.onStart();\n    harness.disconnect();\n    harness.broadcast();\n\n    verify(listener).onConnectivityChanged(eq(false));\n  }\n\n  @Test\n  public void testNotifiesListenerIfDisconnectedAndBecomesConnected() {\n    harness.disconnect();\n\n    monitor.onStart();\n    harness.connect();\n    harness.broadcast();\n\n    verify(listener).onConnectivityChanged(true);\n  }\n\n  @Test\n  public void testDoesNotNotifyListenerWhenNotRegistered() {\n    harness.disconnect();\n\n    monitor.onStart();\n    monitor.onStop();\n    harness.connect();\n    harness.broadcast();\n\n    verify(listener, never()).onConnectivityChanged(anyBoolean());\n  }\n\n  @Test\n  public void register_withMissingPermission_doesNotThrow() {\n    harness.setNetworkPermissionGranted(false);\n\n    monitor.onStart();\n  }\n\n  @Test\n  public void onReceive_withMissingPermission_doesNotThrow() {\n    monitor.onStart();\n    harness.setNetworkPermissionGranted(false);\n    harness.broadcast();\n  }\n\n  @Test\n  public void onReceive_withMissingPermission_previouslyDisconnected_notifiesListenersConnected() {\n    harness.disconnect();\n    monitor.onStart();\n    harness.setNetworkPermissionGranted(false);\n    harness.broadcast();\n\n    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {\n      verify(listener).onConnectivityChanged(true);\n    } else {\n      verify(listener, never()).onConnectivityChanged(anyBoolean());\n    }\n  }\n\n  @Test\n  public void onReceive_withMissingPermission_previouslyConnected_doesNotNotifyListeners() {\n    harness.connect();\n    monitor.onStart();\n    harness.setNetworkPermissionGranted(false);\n    harness.broadcast();\n\n    verify(listener, never()).onConnectivityChanged(anyBoolean());\n  }\n\n  private interface ConnectivityHarness {\n    void connect();\n\n    void disconnect();\n\n    void broadcast();\n\n    void setNetworkPermissionGranted(boolean isGranted);\n\n    int getRegisteredReceivers();\n  }\n\n  private static final class ConnectivityHarnessPost24 implements ConnectivityHarness {\n\n    private final PermissionConnectivityManager shadowConnectivityManager;\n\n    ConnectivityHarnessPost24() {\n      ConnectivityManager connectivityManager =\n          (ConnectivityManager)\n              ApplicationProvider.getApplicationContext()\n                  .getSystemService(Context.CONNECTIVITY_SERVICE);\n      shadowConnectivityManager = Shadow.extract(connectivityManager);\n    }\n\n    @Override\n    public void connect() {\n      shadowConnectivityManager.isConnected = true;\n    }\n\n    @Override\n    public void disconnect() {\n      shadowConnectivityManager.isConnected = false;\n    }\n\n    @Override\n    public void broadcast() {\n      for (NetworkCallback callback : shadowConnectivityManager.getNetworkCallbacks()) {\n        if (shadowConnectivityManager.isConnected) {\n          callback.onAvailable(null);\n        } else {\n          callback.onLost(null);\n        }\n      }\n    }\n\n    @Override\n    public void setNetworkPermissionGranted(boolean isGranted) {\n      shadowConnectivityManager.isNetworkPermissionGranted = isGranted;\n    }\n\n    @Override\n    public int getRegisteredReceivers() {\n      return shadowConnectivityManager.getNetworkCallbacks().size();\n    }\n  }\n\n  private static final class ConnectivityHarnessPre24 implements ConnectivityHarness {\n    private final PermissionConnectivityManager shadowConnectivityManager;\n\n    public ConnectivityHarnessPre24() {\n      ConnectivityManager connectivityManager =\n          (ConnectivityManager)\n              ApplicationProvider.getApplicationContext()\n                  .getSystemService(Context.CONNECTIVITY_SERVICE);\n      shadowConnectivityManager = Shadow.extract(connectivityManager);\n    }\n\n    @Override\n    public void disconnect() {\n      shadowConnectivityManager.setActiveNetworkInfo(null);\n    }\n\n    @Override\n    public void connect() {\n      NetworkInfo networkInfo =\n          ShadowNetworkInfo.newInstance(NetworkInfo.DetailedState.CONNECTED, 0, 0, true, true);\n      shadowConnectivityManager.setActiveNetworkInfo(networkInfo);\n    }\n\n    @Override\n    public void broadcast() {\n      Intent connected = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);\n      ApplicationProvider.getApplicationContext().sendBroadcast(connected);\n    }\n\n    @Override\n    public void setNetworkPermissionGranted(boolean isGranted) {\n      shadowConnectivityManager.isNetworkPermissionGranted = isGranted;\n    }\n\n    @Override\n    public int getRegisteredReceivers() {\n      Intent connectivity = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);\n      return shadowOf((Application) ApplicationProvider.getApplicationContext())\n          .getReceiversForIntent(connectivity)\n          .size();\n    }\n  }\n\n  @Implements(ConnectivityManager.class)\n  public static final class PermissionConnectivityManager extends ShadowConnectivityManager {\n    private boolean isNetworkPermissionGranted = true;\n    private boolean isConnected;\n\n    @Implementation\n    @Override\n    public Network getActiveNetwork() {\n      if (isConnected) {\n        return ShadowNetwork.newInstance(1);\n      } else {\n        return null;\n      }\n    }\n\n    @Implementation\n    @Override\n    protected void registerDefaultNetworkCallback(NetworkCallback networkCallback) {\n      if (!isNetworkPermissionGranted) {\n        throw new SecurityException();\n      }\n      super.registerDefaultNetworkCallback(networkCallback);\n      if (isConnected) {\n        networkCallback.onAvailable(null);\n      } else {\n        networkCallback.onLost(null);\n      }\n    }\n\n    @Implementation\n    @Override\n    public NetworkInfo getActiveNetworkInfo() {\n      if (!isNetworkPermissionGranted) {\n        throw new SecurityException();\n      }\n      return super.getActiveNetworkInfo();\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/manager/Issue117Activity.java",
    "content": "package com.bumptech.glide.manager;\n\nimport static android.view.ViewGroup.LayoutParams.MATCH_PARENT;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentPagerAdapter;\nimport androidx.viewpager.widget.ViewPager;\nimport com.bumptech.glide.Glide;\n\n/** A test activity to reproduce Issue #117: https://github.com/bumptech/glide/issues/117. */\n@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\nclass Issue117Activity extends FragmentActivity {\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    ViewPager viewPager = new ViewPager(this);\n    viewPager.setId(View.generateViewId());\n    setContentView(viewPager, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));\n    viewPager.setAdapter(new Issue117Adapter(getSupportFragmentManager()));\n  }\n\n  private static class Issue117Adapter extends FragmentPagerAdapter {\n\n    Issue117Adapter(FragmentManager fm) {\n      super(fm);\n    }\n\n    @Override\n    public Fragment getItem(int position) {\n      return new Issue117Fragment();\n    }\n\n    @Override\n    public int getCount() {\n      return 1;\n    }\n  }\n\n  public static class Issue117Fragment extends Fragment {\n    @Override\n    public View onCreateView(\n        @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n      return new Issue117ImageView(getActivity());\n    }\n  }\n\n  public static class Issue117ImageView extends ImageView {\n    public Issue117ImageView(Context context) {\n      super(context);\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n      super.onAttachedToWindow();\n      Glide.with(getContext()).asDrawable().load(android.R.drawable.ic_menu_rotate).into(this);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/manager/RequestManagerRetrieverTest.java",
    "content": "package com.bumptech.glide.manager;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.BackgroundUtil.testInBackground;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.when;\nimport static org.robolectric.annotation.LooperMode.Mode.LEGACY;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.view.LayoutInflater;\nimport androidx.annotation.RequiresApi;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.fragment.app.FragmentController;\nimport androidx.fragment.app.FragmentHostCallback;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.tests.BackgroundUtil.BackgroundTester;\nimport com.bumptech.glide.tests.TearDownGlide;\nimport com.bumptech.glide.tests.Util;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mockito;\nimport org.robolectric.Robolectric;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\nimport org.robolectric.android.controller.ActivityController;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.LooperMode;\nimport org.robolectric.shadows.ShadowLooper;\n\n@LooperMode(LEGACY)\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class RequestManagerRetrieverTest {\n  @Rule public TearDownGlide tearDownGlide = new TearDownGlide();\n\n  private static final String PARENT_TAG = \"parent\";\n  private Context appContext;\n  private int initialSdkVersion;\n  private RequestManagerRetriever retriever;\n\n  @Before\n  public void setUp() {\n    appContext = ApplicationProvider.getApplicationContext();\n\n    retriever = new RequestManagerRetriever(/* factory= */ null);\n\n    initialSdkVersion = Build.VERSION.SDK_INT;\n    Util.setSdkVersionInt(18);\n  }\n\n  @After\n  public void tearDown() {\n    Util.setSdkVersionInt(initialSdkVersion);\n\n    Shadows.shadowOf(Looper.getMainLooper()).runToEndOfTasks();\n  }\n\n  @Test\n  public void testHasValidTag() {\n    assertEquals(\n        RequestManagerRetriever.class.getPackage().getName(), RequestManagerRetriever.FRAGMENT_TAG);\n  }\n\n  @Test\n  public void testCanGetRequestManagerFromActivity() {\n    Activity activity = Robolectric.buildActivity(Activity.class).create().start().get();\n    RequestManager manager = retriever.get(activity);\n    assertEquals(manager, retriever.get(activity));\n  }\n\n  @Test\n  public void testSupportCanGetRequestManagerFromActivity() {\n    FragmentActivity fragmentActivity =\n        Robolectric.buildActivity(FragmentActivity.class).create().start().get();\n    RequestManager manager = retriever.get(fragmentActivity);\n    assertEquals(manager, retriever.get(fragmentActivity));\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  @Test\n  public void testCanGetRequestManagerFromFragment() {\n    Activity activity = Robolectric.buildActivity(Activity.class).create().start().resume().get();\n    android.app.Fragment fragment = new android.app.Fragment();\n    activity.getFragmentManager().beginTransaction().add(fragment, PARENT_TAG).commit();\n    activity.getFragmentManager().executePendingTransactions();\n\n    RequestManager manager = retriever.get(fragment);\n    assertEquals(manager, retriever.get(fragment));\n  }\n\n  @Test\n  public void testSupportCanGetRequestManagerFromFragment() {\n    FragmentActivity activity =\n        Robolectric.buildActivity(FragmentActivity.class).create().start().resume().get();\n    Fragment fragment = new Fragment();\n    activity.getSupportFragmentManager().beginTransaction().add(fragment, PARENT_TAG).commit();\n    activity.getSupportFragmentManager().executePendingTransactions();\n\n    RequestManager manager = retriever.get(fragment);\n    assertEquals(manager, retriever.get(fragment));\n  }\n\n  @Test\n  public void testSupportCanGetRequestManagerFromFragment_nonActivityController() {\n    FragmentController controller =\n        FragmentController.createController(new NonActivityHostCallback(appContext));\n    controller.attachHost(/* fragment= */ null);\n    controller.dispatchCreate();\n    controller.dispatchStart();\n    controller.dispatchResume();\n\n    Fragment fragment = new Fragment();\n    controller.getSupportFragmentManager().beginTransaction().add(fragment, PARENT_TAG).commit();\n    controller.getSupportFragmentManager().executePendingTransactions();\n\n    RequestManager manager = retriever.get(fragment);\n    assertEquals(manager, retriever.get(fragment));\n  }\n\n  @Test\n  public void testCanGetRequestManagerFromDetachedFragment() {\n    helpTestCanGetRequestManagerFromDetachedFragment();\n  }\n\n  @Test\n  public void testCanGetRequestManagerFromDetachedFragment_PreJellyBeanMr1() {\n    Util.setSdkVersionInt(Build.VERSION_CODES.JELLY_BEAN);\n    helpTestCanGetRequestManagerFromDetachedFragment();\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  private void helpTestCanGetRequestManagerFromDetachedFragment() {\n    Activity activity = Robolectric.buildActivity(Activity.class).create().start().resume().get();\n    android.app.Fragment fragment = new android.app.Fragment();\n    activity\n        .getFragmentManager()\n        .beginTransaction()\n        .add(fragment, PARENT_TAG)\n        .detach(fragment)\n        .commit();\n    activity.getFragmentManager().executePendingTransactions();\n\n    assertTrue(fragment.isDetached());\n    retriever.get(fragment);\n  }\n\n  @Test\n  public void testSupportCanGetRequestManagerFromDetachedFragment() {\n    helpTestSupportCanGetRequestManagerFromDetachedFragment();\n  }\n\n  @Test\n  public void testSupportCanGetRequestManagerFromDetachedFragment_PreJellyBeanMr1() {\n    Util.setSdkVersionInt(Build.VERSION_CODES.JELLY_BEAN);\n    helpTestSupportCanGetRequestManagerFromDetachedFragment();\n  }\n\n  private void helpTestSupportCanGetRequestManagerFromDetachedFragment() {\n    FragmentActivity activity =\n        Robolectric.buildActivity(FragmentActivity.class).create().start().resume().get();\n    Fragment fragment = new Fragment();\n    activity\n        .getSupportFragmentManager()\n        .beginTransaction()\n        .add(fragment, PARENT_TAG)\n        .detach(fragment)\n        .commit();\n    activity.getSupportFragmentManager().executePendingTransactions();\n\n    assertTrue(fragment.isDetached());\n    retriever.get(fragment);\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfFragmentNotAttached() {\n    android.app.Fragment fragment = new android.app.Fragment();\n    retriever.get(fragment);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfSupportFragmentNotAttached() {\n    Fragment fragment = new Fragment();\n    retriever.get(fragment);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsIfGivenNullContext() {\n    retriever.get((Context) null);\n  }\n\n  @Test\n  public void testHandlesContextWrappersForApplication() {\n    ContextWrapper contextWrapper = new ContextWrapper(appContext);\n    RequestManager requestManager = retriever.get(appContext);\n\n    assertEquals(requestManager, retriever.get(contextWrapper));\n  }\n\n  @Test\n  public void testHandlesContextWrapperWithoutApplication() throws Exception {\n    // Create a Context which is not associated with an Application instance.\n    Context baseContext =\n        appContext.createPackageContext(appContext.getPackageName(), /* flags= */ 0);\n\n    // Sanity-check that Robolectric behaves the same as the framework.\n    assertThat(baseContext.getApplicationContext()).isNull();\n\n    // If a wrapper provides a non-null application Context, unwrapping should terminate at this\n    // wrapper so that the returned Context has a non-null #getApplicationContext.\n    Context contextWithApplicationContext =\n        new ContextWrapper(baseContext) {\n          @Override\n          public Context getApplicationContext() {\n            return this;\n          }\n        };\n\n    Context wrappedContext = new ContextWrapper(contextWithApplicationContext);\n    RequestManager requestManager = retriever.get(appContext);\n\n    assertEquals(requestManager, retriever.get(wrappedContext));\n  }\n\n  @Test\n  public void testReturnsNonNullManagerIfGivenApplicationContext() {\n    assertNotNull(retriever.get(appContext));\n  }\n\n  @Test\n  public void testApplicationRequestManagerIsNotPausedWhenRetrieved() {\n    RequestManager manager = retriever.get(appContext);\n    assertFalse(manager.isPaused());\n  }\n\n  @Test\n  public void testApplicationRequestManagerIsNotReResumedAfterFirstRetrieval() {\n    RequestManager manager = retriever.get(appContext);\n    manager.pauseRequests();\n    manager = retriever.get(appContext);\n    assertTrue(manager.isPaused());\n  }\n\n  @Test\n  public void testDoesNotThrowWhenGetWithContextCalledFromBackgroundThread()\n      throws InterruptedException {\n    testInBackground(\n        new BackgroundTester() {\n          @Override\n          public void runTest() {\n            retriever.get(appContext);\n          }\n        });\n  }\n\n  // See Issue #117: https://github.com/bumptech/glide/issues/117.\n  @Test\n  public void testCanCallGetInOnAttachToWindowInFragmentInViewPager() {\n    // Robolectric by default runs messages posted to the main looper synchronously, the\n    // framework does not. We post\n    // to the main thread here to work around an issue caused by a recursive method call so we\n    // need (and reasonably\n    // expect) our message to not run immediately\n    Shadows.shadowOf(Looper.getMainLooper()).pause();\n    Robolectric.buildActivity(Issue117Activity.class).create().start().resume().visible();\n  }\n\n  @Test\n  @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n  public void testDoesNotThrowIfAskedToGetManagerForActivityPreJellYBeanMr1() {\n    Util.setSdkVersionInt(Build.VERSION_CODES.JELLY_BEAN);\n    Activity activity = Robolectric.buildActivity(Activity.class).create().start().resume().get();\n    Activity spyActivity = Mockito.spy(activity);\n    when(spyActivity.isDestroyed()).thenThrow(new NoSuchMethodError());\n\n    assertNotNull(retriever.get(spyActivity));\n  }\n\n  @SuppressWarnings(\"deprecation\")\n  @Test\n  @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n  public void testDoesNotThrowIfAskedToGetManagerForFragmentPreJellyBeanMr1() {\n    Util.setSdkVersionInt(Build.VERSION_CODES.JELLY_BEAN);\n    Activity activity = Robolectric.buildActivity(Activity.class).create().start().resume().get();\n    android.app.Fragment fragment = new android.app.Fragment();\n\n    activity.getFragmentManager().beginTransaction().add(fragment, \"test\").commit();\n    android.app.Fragment spyFragment = Mockito.spy(fragment);\n    when(spyFragment.getChildFragmentManager()).thenThrow(new NoSuchMethodError());\n\n    assertNotNull(retriever.get(spyFragment));\n  }\n\n  @Test\n  public void get_beforeActivityIsCreated_returnsSameRequestManagerAsAfterActivityIsCreated() {\n    ShadowLooper shadowLooper = Shadows.shadowOf(Looper.getMainLooper());\n    shadowLooper.pause();\n    ActivityController<FragmentActivity> controller =\n        Robolectric.buildActivity(FragmentActivity.class);\n    RequestManager beforeCreateRequestManager = Glide.with(controller.get());\n    // Make sure that the activity makes it one frame without being created.\n    controller.create().start();\n    // Simulate finishing at least one frame before the next attempt to get a RequestManager\n    shadowLooper.runOneTask();\n\n    // Try to get the request manager again. If we've successfully retained the Fragment we wanted\n    // to add, then we should get the same instance. If we added a new Fragment instance, the\n    // RequestManager won't match and things will be broken.\n    RequestManager afterCreateRequestManager = Glide.with(controller.get());\n    assertThat(afterCreateRequestManager).isEqualTo(beforeCreateRequestManager);\n  }\n\n  @Test\n  public void get_onDetachedFragment_returnsSameRequestManagerAsAfterFragmentIsAttached() {\n    ShadowLooper shadowLooper = Shadows.shadowOf(Looper.getMainLooper());\n    shadowLooper.pause();\n    ActivityController<FragmentActivity> controller =\n        Robolectric.buildActivity(FragmentActivity.class);\n    controller.create();\n\n    FragmentActivity fragmentActivity = controller.get();\n    Fragment childFragment = new Fragment();\n    fragmentActivity\n        .getSupportFragmentManager()\n        .beginTransaction()\n        .add(childFragment, \"TEST_TAG\")\n        .commitNow();\n    fragmentActivity\n        .getSupportFragmentManager()\n        .beginTransaction()\n        .detach(childFragment)\n        .commitNow();\n\n    RequestManager beforeAttachRequestManager = Glide.with(childFragment);\n    shadowLooper.runOneTask();\n    fragmentActivity\n        .getSupportFragmentManager()\n        .beginTransaction()\n        .attach(childFragment)\n        .commitNow();\n\n    RequestManager afterAttachRequestManager = Glide.with(childFragment);\n    assertThat(afterAttachRequestManager).isEqualTo(beforeAttachRequestManager);\n  }\n\n  /** Simple callback for creating an Activity-less Fragment host. */\n  private final class NonActivityHostCallback\n      extends FragmentHostCallback<RequestManagerRetrieverTest> {\n\n    private final Context context;\n\n    NonActivityHostCallback(Context context) {\n      super(context, new Handler(Looper.getMainLooper()), /* windowAnimations= */ 0);\n      this.context = context;\n    }\n\n    @Override\n    public LayoutInflater onGetLayoutInflater() {\n      return LayoutInflater.from(context).cloneInContext(context);\n    }\n\n    @Override\n    public RequestManagerRetrieverTest onGetHost() {\n      return RequestManagerRetrieverTest.this;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/manager/RequestTrackerTest.java",
    "content": "package com.bumptech.glide.manager;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.bumptech.glide.request.Request;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class RequestTrackerTest {\n  private RequestTracker tracker;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    tracker = new RequestTracker();\n  }\n\n  @Test\n  public void clearAndRemove_withRequestPreviouslyClearedInClearRequests_doesNothing() {\n    FakeRequest request = new FakeRequest();\n    tracker.addRequest(request);\n\n    tracker.clearRequests();\n    tracker.clearAndRemove(request);\n\n    assertThat(request.isCleared()).isTrue();\n  }\n\n  @Test\n  public void clearAndRemove_withNullRequest_doesNothingAndReturnsTrue() {\n    assertThat(tracker.clearAndRemove(null)).isTrue();\n  }\n\n  @Test\n  public void clearAndRemove_withUnTrackedRequest_doesNothingAndReturnsFalse() {\n    FakeRequest request = new FakeRequest();\n\n    assertThat(tracker.clearAndRemove(request)).isFalse();\n\n    assertThat(request.isCleared()).isFalse();\n  }\n\n  @Test\n  public void clearAndRemov_withTrackedRequest_clearssAndReturnsTrue() {\n    FakeRequest request = new FakeRequest();\n    tracker.addRequest(request);\n\n    assertThat(tracker.clearAndRemove(request)).isTrue();\n    assertThat(request.isCleared()).isTrue();\n  }\n\n  @Test\n  public void clearAndRemove_withAlreadyRemovedRequest_doesNothingAndReturnsFalse() {\n    FakeRequest request = new FakeRequest();\n    tracker.addRequest(request);\n    tracker.clearAndRemove(request);\n    assertThat(tracker.clearAndRemove(request)).isFalse();\n\n    assertThat(request.isCleared()).isTrue();\n  }\n\n  @Test\n  public void clearRequests_withPreviouslyClearedRequest_doesNotClearRequestAgain() {\n    FakeRequest request = new FakeRequest();\n    tracker.addRequest(request);\n    tracker.clearAndRemove(request);\n\n    tracker.clearRequests();\n\n    assertThat(request.isCleared()).isTrue();\n  }\n\n  @Test\n  public void clearRequests_withMultipleRequests_clearsAllRequests() {\n    FakeRequest first = new FakeRequest();\n    FakeRequest second = new FakeRequest();\n    tracker.addRequest(first);\n    tracker.addRequest(second);\n\n    tracker.clearRequests();\n\n    assertThat(first.isCleared()).isTrue();\n    assertThat(second.isCleared()).isTrue();\n  }\n\n  @Test\n  public void pauseRequest_withRunningRequest_pausesRequest() {\n    FakeRequest request = new FakeRequest();\n    request.setIsRunning();\n    tracker.addRequest(request);\n\n    tracker.pauseRequests();\n\n    assertThat(request.isPaused()).isTrue();\n  }\n\n  @Test\n  public void pauseRequests_withCompletedRequest_doesNotPauseRequest() {\n    FakeRequest request = new FakeRequest();\n    tracker.addRequest(request);\n\n    request.setIsComplete();\n    tracker.pauseRequests();\n\n    assertThat(request.isPaused()).isFalse();\n  }\n\n  @Test\n  public void pauseRequests_withClearedRequest_doesNotPauseRequest() {\n    FakeRequest request = new FakeRequest();\n    tracker.addRequest(request);\n\n    request.clear();\n    tracker.pauseRequests();\n\n    assertThat(request.isPaused()).isFalse();\n  }\n\n  @Test\n  public void runRequest_startsRequest() {\n    FakeRequest request = new FakeRequest();\n    tracker.runRequest(request);\n\n    assertThat(request.isRunning()).isTrue();\n  }\n\n  @Test\n  public void runRequest_whenPaused_doesNotStartRequest() {\n    FakeRequest request = new FakeRequest();\n    tracker.pauseRequests();\n    tracker.runRequest(request);\n\n    assertThat(request.isRunning()).isFalse();\n  }\n\n  @Test\n  public void runRequest_withAllRequestsPaused_doesNotStartRequest() {\n    FakeRequest request = new FakeRequest();\n    tracker.pauseAllRequests();\n    tracker.runRequest(request);\n\n    assertThat(request.isRunning()).isFalse();\n  }\n\n  @Test\n  public void runRequest_afterPausingAndResuming_startsRequest() {\n    FakeRequest request = new FakeRequest();\n    tracker.pauseRequests();\n    tracker.runRequest(request);\n    tracker.resumeRequests();\n\n    assertThat(request.isRunning()).isTrue();\n  }\n\n  @Test\n  public void resumeRequests_withRequestAddedWhilePaused_startsRequest() {\n    FakeRequest request = new FakeRequest();\n    tracker.addRequest(request);\n\n    tracker.resumeRequests();\n\n    assertThat(request.isRunning()).isTrue();\n  }\n\n  @Test\n  public void resumeRequests_withCompletedRequest_doesNotRestartCompletedRequest() {\n    FakeRequest request = new FakeRequest();\n    request.setIsComplete();\n    tracker.addRequest(request);\n\n    tracker.resumeRequests();\n\n    assertThat(request.isRunning()).isFalse();\n  }\n\n  @Test\n  public void addRequest_withRunningRequest_doesNotRestartRequest() {\n    FakeRequest request = new FakeRequest();\n    request.setIsRunning();\n    tracker.addRequest(request);\n\n    tracker.resumeRequests();\n\n    assertThat(request.isRunning()).isTrue();\n  }\n\n  @Test\n  public void resumeRequests_withRequestThatClearsAnotherRequest_avoidsConcurrentModifications() {\n    Request first = mock(Request.class);\n    Request second = mock(Request.class);\n\n    doAnswer(new ClearAndRemoveRequest(second)).when(first).begin();\n\n    tracker.addRequest(mock(Request.class));\n    tracker.addRequest(first);\n    tracker.addRequest(second);\n\n    tracker.resumeRequests();\n  }\n\n  @Test\n  public void pauseRequests_withRequestThatClearsAnother_avoidsConcurrentModifications() {\n    Request first = mock(Request.class);\n    Request second = mock(Request.class);\n\n    when(first.isRunning()).thenReturn(true);\n    doAnswer(new ClearAndRemoveRequest(second)).when(first).clear();\n\n    tracker.addRequest(mock(Request.class));\n    tracker.addRequest(first);\n    tracker.addRequest(second);\n\n    tracker.pauseRequests();\n  }\n\n  @Test\n  public void clearRequests_withRequestThatClearsAnother_avoidsConcurrentModifications() {\n    Request first = mock(Request.class);\n    Request second = mock(Request.class);\n\n    doAnswer(new ClearAndRemoveRequest(second)).when(first).clear();\n\n    tracker.addRequest(mock(Request.class));\n    tracker.addRequest(first);\n    tracker.addRequest(second);\n\n    tracker.clearRequests();\n  }\n\n  @Test\n  public void restartRequests_withRequestThatClearsAnother_avoidsConcurrentModifications() {\n    Request first = mock(Request.class);\n    Request second = mock(Request.class);\n\n    doAnswer(new ClearAndRemoveRequest(second)).when(first).clear();\n\n    tracker.addRequest(mock(Request.class));\n    tracker.addRequest(first);\n    tracker.addRequest(second);\n\n    tracker.restartRequests();\n  }\n\n  @Test\n  public void restartRequests_withIncompleteRequest_restartsRequest() {\n    FakeRequest request = new FakeRequest();\n    tracker.addRequest(request);\n\n    tracker.restartRequests();\n\n    assertThat(request.isRunning()).isTrue();\n  }\n\n  @Test\n  public void restartRequests_whenPaused_doesNotRestartRequests() {\n    FakeRequest request = new FakeRequest();\n    request.setIsComplete();\n    tracker.pauseRequests();\n    tracker.addRequest(request);\n\n    tracker.restartRequests();\n\n    assertThat(request.isRunning()).isFalse();\n  }\n\n  @Test\n  public void restartRequests_withIncompleteRequestAddedWhilePaused_doesNotRestartRequest() {\n    FakeRequest request = new FakeRequest();\n\n    tracker.pauseRequests();\n    tracker.addRequest(request);\n    tracker.restartRequests();\n\n    assertThat(request.isRunning()).isFalse();\n  }\n\n  @Test\n  public void restartRequests_withIncompleteRequestAddedWhilePaused_clearsRequestOnRestart() {\n    FakeRequest request = new FakeRequest();\n\n    tracker.pauseRequests();\n    tracker.addRequest(request);\n    tracker.restartRequests();\n\n    assertThat(request.isCleared()).isTrue();\n  }\n\n  @Test\n  public void testReturnsTrueFromIsPausedWhenPaused() {\n    tracker.pauseRequests();\n    assertTrue(tracker.isPaused());\n  }\n\n  @Test\n  public void pauseRequests_pausesRunningRequest() {\n    FakeRequest request = new FakeRequest();\n    request.setIsRunning();\n    tracker.addRequest(request);\n    tracker.pauseRequests();\n\n    assertThat(request.isCleared()).isTrue();\n  }\n\n  @Test\n  public void pauseRequest_doesNotPauseCompletedRequest() {\n    FakeRequest request = new FakeRequest();\n    request.setIsComplete();\n    tracker.addRequest(request);\n    tracker.pauseRequests();\n\n    assertThat(request.isComplete()).isTrue();\n    assertThat(request.isCleared()).isFalse();\n  }\n\n  @Test\n  public void testReturnsFalseFromIsPausedWhenResumed() {\n    tracker.resumeRequests();\n    assertFalse(tracker.isPaused());\n  }\n\n  @Test\n  public void testPauseAllRequests_returnsTrueFromIsPaused() {\n    tracker.pauseAllRequests();\n    assertTrue(tracker.isPaused());\n  }\n\n  @Test\n  public void resumeRequests_afterRequestIsPausedViaPauseAllRequests_resumesRequest() {\n    FakeRequest request = new FakeRequest();\n    request.setIsComplete();\n\n    tracker.addRequest(request);\n    tracker.pauseAllRequests();\n\n    assertThat(request.isCleared()).isTrue();\n\n    // reset complete status.\n    request.setIsComplete(false);\n    tracker.resumeRequests();\n\n    assertThat(request.isRunning()).isTrue();\n  }\n\n  private static final class FakeRequest implements Request {\n\n    private boolean isPaused;\n\n    @Override\n    public void pause() {\n      isPaused = true;\n      if (isRunning) {\n        clear();\n      }\n    }\n\n    private boolean isRunning;\n    private boolean isCleared;\n    private boolean isComplete;\n\n    void setIsComplete() {\n      setIsComplete(true);\n    }\n\n    void setIsComplete(boolean isComplete) {\n      this.isComplete = isComplete;\n    }\n\n    void setIsRunning() {\n      isRunning = true;\n    }\n\n    boolean isPaused() {\n      return isPaused;\n    }\n\n    @Override\n    public void begin() {\n      if (isRunning) {\n        throw new IllegalStateException();\n      }\n      isRunning = true;\n    }\n\n    @Override\n    public void clear() {\n      if (isCleared) {\n        throw new IllegalStateException();\n      }\n      isRunning = false;\n      isCleared = true;\n    }\n\n    @Override\n    public boolean isRunning() {\n      return isRunning;\n    }\n\n    @Override\n    public boolean isComplete() {\n      return isComplete;\n    }\n\n    @Override\n    public boolean isCleared() {\n      return isCleared;\n    }\n\n    @Override\n    public boolean isAnyResourceSet() {\n      return isComplete;\n    }\n\n    @Override\n    public boolean isEquivalentTo(Request other) {\n      throw new UnsupportedOperationException();\n    }\n  }\n\n  private class ClearAndRemoveRequest implements Answer<Void> {\n\n    private final Request toRemove;\n\n    ClearAndRemoveRequest(Request toRemove) {\n      this.toRemove = toRemove;\n    }\n\n    @Override\n    public Void answer(InvocationOnMock invocationOnMock) throws Throwable {\n      tracker.clearAndRemove(toRemove);\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/module/ManifestParserTest.java",
    "content": "package com.bumptech.glide.module;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.Registry;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\n@SuppressWarnings(\"deprecation\")\npublic class ManifestParserTest {\n  private static final String MODULE_VALUE = \"GlideModule\";\n  private static final String PACKAGE_NAME = \"com.bumptech.test\";\n\n  @Mock private Context context;\n  private ManifestParser parser;\n  private ApplicationInfo applicationInfo;\n\n  @Before\n  public void setUp() throws PackageManager.NameNotFoundException {\n    MockitoAnnotations.initMocks(this);\n    applicationInfo = new ApplicationInfo();\n    applicationInfo.metaData = new Bundle();\n\n    when(context.getPackageName()).thenReturn(PACKAGE_NAME);\n\n    PackageManager pm = mock(PackageManager.class);\n    when(pm.getApplicationInfo(eq(PACKAGE_NAME), eq(PackageManager.GET_META_DATA)))\n        .thenReturn(applicationInfo);\n    when(context.getPackageManager()).thenReturn(pm);\n\n    parser = new ManifestParser(context);\n  }\n\n  // TODO(#4977): Remove this after the bug in Compose's previews is fixed.\n  @Test\n  public void parse_withNullApplicationInfo_doesNotThrow() throws NameNotFoundException {\n    PackageManager pm = mock(PackageManager.class);\n    when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(null);\n    when(context.getPackageManager()).thenReturn(pm);\n\n    parser = new ManifestParser(context);\n    parser.parse();\n  }\n\n  @Test\n  public void testParse_returnsEmptyListIfNoModulesListed() {\n    assertThat(parser.parse()).isEmpty();\n  }\n\n  @Test\n  public void testParse_withSingleValidModuleName_returnsListContainingModule() {\n    addModuleToManifest(TestModule1.class);\n\n    List<GlideModule> modules = parser.parse();\n    assertThat(modules).hasSize(1);\n    assertThat(modules.get(0)).isInstanceOf(TestModule1.class);\n  }\n\n  @Test\n  public void testParse_withMultipleValidModuleNames_returnsListContainingModules() {\n    addModuleToManifest(TestModule1.class);\n    addModuleToManifest(TestModule2.class);\n\n    List<GlideModule> modules = parser.parse();\n    assertThat(modules).hasSize(2);\n\n    assertThat(modules).contains(new TestModule1());\n    assertThat(modules).contains(new TestModule2());\n  }\n\n  @Test\n  public void testParse_withValidModuleName_ignoresMetadataWithoutGlideModuleValue() {\n    applicationInfo.metaData.putString(TestModule1.class.getName(), MODULE_VALUE + \"test\");\n    assertThat(parser.parse()).isEmpty();\n  }\n\n  @Test(expected = RuntimeException.class)\n  public void testThrows_whenModuleNameNotFound() {\n    addToManifest(\"fakeClassName\");\n\n    parser.parse();\n  }\n\n  @Test(expected = RuntimeException.class)\n  public void testThrows_whenClassInManifestIsNotAModule() {\n    addModuleToManifest(InvalidClass.class);\n\n    parser.parse();\n  }\n\n  @Test\n  public void parse_withNullMetadata_doesNotThrow() throws NameNotFoundException {\n    PackageManager pm = mock(PackageManager.class);\n    ApplicationInfo applicationInfo = new ApplicationInfo();\n    applicationInfo.metaData = null;\n    when(pm.getApplicationInfo(eq(PACKAGE_NAME), eq(PackageManager.GET_META_DATA)))\n        .thenReturn(applicationInfo);\n    when(context.getPackageManager()).thenReturn(pm);\n\n    parser.parse();\n  }\n\n  @Test\n  public void parse_withMissingName_doesNotThrow() throws NameNotFoundException {\n    PackageManager pm = mock(PackageManager.class);\n    doThrow(new NameNotFoundException(\"name\")).when(pm).getApplicationInfo(anyString(), anyInt());\n    when(context.getPackageManager()).thenReturn(pm);\n\n    parser.parse();\n  }\n\n  private void addModuleToManifest(Class<?> moduleClass) {\n    addToManifest(moduleClass.getName());\n  }\n\n  private void addToManifest(String key) {\n    applicationInfo.metaData.putString(key, MODULE_VALUE);\n  }\n\n  private static class InvalidClass {}\n\n  public static class TestModule1 implements GlideModule {\n    @Override\n    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {}\n\n    @Override\n    public void registerComponents(Context context, Glide glide, Registry registry) {}\n\n    @Override\n    public boolean equals(Object o) {\n      return o instanceof TestModule1;\n    }\n\n    @Override\n    public int hashCode() {\n      return super.hashCode();\n    }\n  }\n\n  public static class TestModule2 implements GlideModule {\n\n    @Override\n    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {}\n\n    @Override\n    public void registerComponents(Context context, Glide glide, Registry registry) {}\n\n    @Override\n    public boolean equals(Object o) {\n      return o instanceof TestModule2;\n    }\n\n    @Override\n    public int hashCode() {\n      return super.hashCode();\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/ErrorRequestCoordinatorTest.java",
    "content": "package com.bumptech.glide.request;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport androidx.annotation.Nullable;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n@RunWith(JUnit4.class)\npublic class ErrorRequestCoordinatorTest {\n\n  @Mock private Request primary;\n  @Mock private Request error;\n  @Mock private RequestCoordinator parent;\n  private ErrorRequestCoordinator coordinator;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    coordinator = newCoordinator();\n    coordinator.setRequests(primary, error);\n  }\n\n  @Test\n  public void begin_startsPrimary() {\n    coordinator.begin();\n    verify(primary).begin();\n  }\n\n  @Test\n  public void begin_whenPrimaryIsAlreadyRunning_doesNotStartPrimaryAgain() {\n    coordinator.begin();\n    coordinator.begin();\n    verify(primary, times(1)).begin();\n  }\n\n  @Test\n  public void clear_whenPrimaryHasNotFailed_clearsPrimary() {\n    coordinator.clear();\n    verify(primary).clear();\n  }\n\n  @Test\n  public void clear_whenPrimaryHasNotFailed_doesNotClearError() {\n    coordinator.clear();\n    verify(error, never()).clear();\n  }\n\n  @Test\n  public void clear_whenPrimaryHasFailed_errorIsRunning_clearsError() {\n    coordinator.onRequestFailed(primary);\n    coordinator.clear();\n    verify(error).clear();\n  }\n\n  @Test\n  public void clear_whenPrimaryHasFailed_clearsPrimary() {\n    coordinator.onRequestFailed(primary);\n    coordinator.clear();\n    verify(primary).clear();\n  }\n\n  @Test\n  public void clear_whenErrorIsRunning_clearsError() {\n    coordinator.onRequestFailed(primary);\n    coordinator.clear();\n\n    verify(error).clear();\n  }\n\n  @Test\n  public void pause_whenPrimaryIsRunning_pausesPrimary() {\n    coordinator.begin();\n    coordinator.pause();\n\n    verify(primary).pause();\n  }\n\n  @Test\n  public void pause_whenPrimaryIsComplete_doesNotPausePrimary() {\n    coordinator.onRequestSuccess(primary);\n    coordinator.pause();\n\n    verify(primary, never()).pause();\n  }\n\n  @Test\n  public void pause_whenPrimaryIsFailed_doesNotPausePrimary() {\n    coordinator.onRequestFailed(primary);\n    coordinator.pause();\n\n    verify(primary, never()).pause();\n  }\n\n  @Test\n  public void pause_whenErrorIsNotRunning_doesNotPauseError() {\n    coordinator.pause();\n\n    verify(error, never()).pause();\n  }\n\n  @Test\n  public void pause_whenErrorIsComplete_doesNotPauseError() {\n    coordinator.onRequestSuccess(error);\n    coordinator.pause();\n\n    verify(error, never()).pause();\n  }\n\n  @Test\n  public void pause_whenErrorIsFailed_doesNotPauseError() {\n    coordinator.onRequestFailed(error);\n    coordinator.pause();\n\n    verify(error, never()).pause();\n  }\n\n  @Test\n  public void pause_whenErrorIsRunning_pausesError() {\n    coordinator.onRequestFailed(primary);\n    coordinator.pause();\n\n    verify(error).pause();\n  }\n\n  @Test\n  public void isRunning_primaryNotFailed_primaryNotRunning_returnsFalse() {\n    assertThat(coordinator.isRunning()).isFalse();\n  }\n\n  @Test\n  public void isRunning_primaryNotFailed_primaryRunning_returnsTrue() {\n    coordinator.begin();\n    assertThat(coordinator.isRunning()).isTrue();\n  }\n\n  @Test\n  public void isRunning_primaryFailed_returnsTrue() {\n    coordinator.onRequestFailed(primary);\n    // A failed primary request starts the error request.\n    assertThat(coordinator.isRunning()).isTrue();\n  }\n\n  @Test\n  public void isComplete_primaryNotFailed_primaryNotComplete_returnsFalse() {\n    assertThat(coordinator.isComplete()).isFalse();\n  }\n\n  @Test\n  public void isComplete_primaryNotFailed_primaryComplete_returnsTrue() {\n    coordinator.onRequestSuccess(primary);\n    assertThat(coordinator.isComplete()).isTrue();\n  }\n\n  @Test\n  public void isComplete_primaryFailed_errorNotComplete_returnsFalse() {\n    coordinator.onRequestFailed(primary);\n    assertThat(coordinator.isComplete()).isFalse();\n  }\n\n  @Test\n  public void isComplete_primaryFailed_errorComplete_returnsTrue() {\n    coordinator.onRequestFailed(primary);\n    coordinator.onRequestSuccess(error);\n    assertThat(coordinator.isComplete()).isTrue();\n  }\n\n  @Test\n  public void isCleared_primaryNotFailed_primaryNotCancelled_returnsFalse() {\n    coordinator.begin();\n    assertThat(coordinator.isCleared()).isFalse();\n  }\n\n  @Test\n  public void isCleared_primaryNotFailed_primaryCancelled_returnsTrue() {\n    coordinator.begin();\n    coordinator.clear();\n    assertThat(coordinator.isCleared()).isTrue();\n  }\n\n  @Test\n  public void isCleared_primaryFailed_errorNotCancelled_returnsFalse() {\n    coordinator.onRequestFailed(primary);\n    assertThat(coordinator.isCleared()).isFalse();\n  }\n\n  @Test\n  public void isCleared_primaryFailed_errorCancelled_returnsTrue() {\n    coordinator.onRequestFailed(primary);\n    coordinator.clear();\n    assertThat(coordinator.isCleared()).isTrue();\n  }\n\n  @Test\n  public void isEquivalentTo() {\n    assertThat(coordinator.isEquivalentTo(primary)).isFalse();\n\n    ErrorRequestCoordinator other = newCoordinator(/* parent= */ null);\n    assertThat(coordinator.isEquivalentTo(other)).isFalse();\n\n    other.setRequests(primary, primary);\n    assertThat(coordinator.isEquivalentTo(other)).isFalse();\n\n    other.setRequests(error, error);\n    assertThat(coordinator.isEquivalentTo(other)).isFalse();\n\n    when(primary.isEquivalentTo(primary)).thenReturn(true);\n    when(error.isEquivalentTo(error)).thenReturn(true);\n    other.setRequests(primary, error);\n    assertThat(coordinator.isEquivalentTo(other)).isTrue();\n\n    other = newCoordinator(parent);\n    other.setRequests(primary, error);\n    assertThat(coordinator.isEquivalentTo(other)).isTrue();\n\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    other = newCoordinator(parent);\n    other.setRequests(primary, error);\n    assertThat(coordinator.isEquivalentTo(other)).isTrue();\n  }\n\n  @Test\n  public void canSetImage_withNotFailedPrimary_andNullParent_returnsTrue() {\n    assertThat(coordinator.canSetImage(primary)).isTrue();\n  }\n\n  @Test\n  public void canSetImage_withNotFailedPrimary_parentCanSetImage_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canSetImage(coordinator)).thenReturn(true);\n\n    assertThat(coordinator.canSetImage(primary)).isTrue();\n  }\n\n  @Test\n  public void canSetImage_withNotFailedPrimary_parentCanNotSetImage_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n\n    assertThat(coordinator.canSetImage(primary)).isFalse();\n  }\n\n  @Test\n  public void canSetImage_withError_andFailedPrimary_nullParent_returnsTrue() {\n    coordinator.onRequestFailed(primary);\n    assertThat(coordinator.canSetImage(error)).isTrue();\n  }\n\n  @Test\n  public void canSetImage_withError_andFailedPrimary_nonNullParentCanSetImage_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canSetImage(coordinator)).thenReturn(true);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canSetImage(error)).isTrue();\n  }\n\n  @Test\n  public void canSetImage_withError_andFailedPrimary_nonNullParentCanNotSetImage_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canSetImage(error)).isFalse();\n  }\n\n  @Test\n  public void canNotifyStatusChanged_withNotFailedPrimary_nullParent_returnsTrue() {\n    assertThat(coordinator.canNotifyStatusChanged(primary)).isTrue();\n  }\n\n  @Test\n  public void canNotifyStatusChanged_withNotFailedPrimary_nonNullParentCantNotify_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n\n    assertThat(coordinator.canNotifyStatusChanged(primary)).isFalse();\n  }\n\n  @Test\n  public void canNotifyStatusChanged_withNotFailedPrimary_nonNullParentCanNotify_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canNotifyStatusChanged(coordinator)).thenReturn(true);\n\n    assertThat(coordinator.canNotifyStatusChanged(primary)).isTrue();\n  }\n\n  @Test\n  public void canNotifyStatusChanged_withError_notFailedPrimary_nullParent_returnsFalse() {\n    assertThat(coordinator.canNotifyStatusChanged(error)).isFalse();\n  }\n\n  @Test\n  public void\n      canNotifyStatusChanged_withErrorRequest_failedPrimary_nullParent_errorIsNotFailed_returnsFalse() {\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyStatusChanged(error)).isFalse();\n  }\n\n  @Test\n  public void\n      canNotifyStatusChanged_withErrorRequest_failedPrimary_nullParent_failedError_returnsTrue() {\n    coordinator.onRequestFailed(primary);\n    coordinator.onRequestFailed(error);\n\n    assertThat(coordinator.canNotifyStatusChanged(error)).isTrue();\n  }\n\n  @Test\n  public void canNotifyStatusChanged_withError_failedPrimary_nonNullParentCantNotify_false() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyStatusChanged(error)).isFalse();\n  }\n\n  @Test\n  public void\n      canNotifyStatusChanged_withError_failedPrimary_notFailedError_nonNullParentCanNotify_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    coordinator.onRequestFailed(primary);\n    when(parent.canNotifyStatusChanged(coordinator)).thenReturn(true);\n\n    assertThat(coordinator.canNotifyStatusChanged(error)).isFalse();\n  }\n\n  @Test\n  public void\n      canNotifyStatusChanged_withError_failedPrimary_failedError_nonNullParentCanNotify_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    coordinator.onRequestFailed(primary);\n    when(parent.canNotifyStatusChanged(coordinator)).thenReturn(true);\n    coordinator.onRequestFailed(error);\n\n    assertThat(coordinator.canNotifyStatusChanged(error)).isTrue();\n  }\n\n  @Test\n  public void isAnyResourceSet_primaryNotSet_nullParent_returnsFalse() {\n    assertThat(coordinator.isAnyResourceSet()).isFalse();\n  }\n\n  @Test\n  public void isAnyResourceSet_primarySet_nullParent_returnsTrue() {\n    when(primary.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(primary);\n    assertThat(coordinator.isAnyResourceSet()).isTrue();\n  }\n\n  @Test\n  public void isAnyResourceSet_primarySet_parentResourceNotSet_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(primary.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(primary);\n\n    assertThat(coordinator.isAnyResourceSet()).isTrue();\n  }\n\n  @Test\n  public void isAnyResourceSet_primarySet_parentSet_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(primary.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(primary);\n    when(parent.isAnyResourceSet()).thenReturn(true);\n\n    assertThat(coordinator.isAnyResourceSet()).isTrue();\n  }\n\n  @Test\n  public void isAnyResourceSet_parentSet_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.isAnyResourceSet()).thenReturn(true);\n\n    assertThat(coordinator.isAnyResourceSet()).isFalse();\n  }\n\n  @Test\n  public void isAnyResourceSet_errorSet_failedPrimary_nullParent_returnsTrue() {\n    coordinator.onRequestFailed(primary);\n    when(error.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(error);\n    assertThat(coordinator.isAnyResourceSet()).isTrue();\n  }\n\n  @Test\n  public void isAnyResourceSet_errorSet_failedPrimary_nonNullParentNotSet_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    coordinator.onRequestFailed(primary);\n    when(error.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(error);\n\n    assertThat(coordinator.isAnyResourceSet()).isTrue();\n  }\n\n  @Test\n  public void isAnyResourceSet_errorSet_nonNullParentSet_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.isAnyResourceSet()).thenReturn(true);\n    when(error.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(error);\n\n    assertThat(coordinator.isAnyResourceSet()).isTrue();\n  }\n\n  @Test\n  public void isAnyResourceSet_primaryNotSet_errorNotSet_nonNullParentNotSet_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n\n    assertThat(coordinator.isAnyResourceSet()).isFalse();\n  }\n\n  @Test\n  public void isAnyResourceSet_primaryNotSet_errorNotSet_nonNullParentSet_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n\n    when(parent.isAnyResourceSet()).thenReturn(true);\n\n    assertThat(coordinator.isAnyResourceSet()).isFalse();\n  }\n\n  @Test\n  public void onRequestSuccess_nullParent_doesNotThrow() {\n    coordinator.onRequestSuccess(primary);\n  }\n\n  @Test\n  public void onRequestSuccess_nonNullParent_callsParent() {\n    coordinator = newCoordinator(parent);\n    coordinator.onRequestSuccess(primary);\n    verify(parent).onRequestSuccess(coordinator);\n  }\n\n  @Test\n  public void onRequestFailed_primaryRequest_notRunningError_beingsError() {\n    coordinator.onRequestFailed(primary);\n    verify(error).begin();\n  }\n\n  @Test\n  public void onRequestFailed_errorRequest_doesNotBeginError() {\n    coordinator.onRequestFailed(error);\n    verify(error, never()).begin();\n  }\n\n  @Test\n  public void onRequestFailed_primaryRequest_notRunningError_nonNullParent_doesNotNotifyParent() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n\n    coordinator.onRequestFailed(primary);\n    verify(parent, never()).onRequestFailed(any(Request.class));\n  }\n\n  @Test\n  public void onRequestFailed_errorRequest_nonNullParent_notifiesParent() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n\n    coordinator.onRequestFailed(error);\n\n    verify(parent).onRequestFailed(coordinator);\n  }\n\n  @Test\n  public void onRequestFailed_primaryRequest_runningError_nonNullParent_doesNotNotifyParent() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    coordinator.onRequestFailed(primary);\n\n    coordinator.onRequestFailed(primary);\n\n    verify(parent, never()).onRequestFailed(any(Request.class));\n  }\n\n  @Test\n  public void canNotifyCleared_primaryRequest_nullParent_returnsTrue() {\n    assertThat(coordinator.canNotifyCleared(primary)).isTrue();\n  }\n\n  @Test\n  public void canNotifyCleared_primaryRequest_parentCanNotNotify_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n\n    assertThat(coordinator.canNotifyCleared(primary)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_primaryRequest_parentCanNotify_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canNotifyCleared(coordinator)).thenReturn(true);\n\n    assertThat(coordinator.canNotifyCleared(primary)).isTrue();\n  }\n\n  @Test\n  public void canNotifyCleared_primaryRequestFailed_parentCanNotify_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canNotifyCleared(coordinator)).thenReturn(true);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyCleared(primary)).isTrue();\n  }\n\n  @Test\n  public void canNotifyCleared_primaryRequestFailed_parentCanNotNotify_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyCleared(primary)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_primaryRequestFailed_nullParent_returnsTrue() {\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyCleared(primary)).isTrue();\n  }\n\n  @Test\n  public void canNotifyCleared_errorRequest_nullParent_returnsFalse() {\n    assertThat(coordinator.canNotifyCleared(error)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_errorRequest_primaryFailed_nullParent_returnsFalse() {\n    coordinator.onRequestFailed(primary);\n    assertThat(coordinator.canNotifyCleared(error)).isFalse();\n  }\n\n  @Test\n  public void\n      canNotifyCleared_primaryRequest_primaryFailed_nonNullParentCanNotNotify_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canNotifyCleared(coordinator)).thenReturn(false);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyCleared(primary)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_errorRequest_primaryFailed_nonNullParentCanNotNotify_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canNotifyCleared(coordinator)).thenReturn(false);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyCleared(error)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_errorRequest_primaryFailed_nonNullParentCanNotify_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canNotifyCleared(coordinator)).thenReturn(true);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyCleared(error)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_primaryRequest_primaryFailed_nonNullParentCanNotify_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(primary, error);\n    when(parent.canNotifyCleared(coordinator)).thenReturn(true);\n    coordinator.onRequestFailed(primary);\n\n    assertThat(coordinator.canNotifyCleared(primary)).isTrue();\n  }\n\n  private static ErrorRequestCoordinator newCoordinator() {\n    return newCoordinator(/* parent= */ null);\n  }\n\n  private static ErrorRequestCoordinator newCoordinator(@Nullable RequestCoordinator parent) {\n    return new ErrorRequestCoordinator(/* requestLock= */ new Object(), parent);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/RequestFutureTargetTest.java",
    "content": "package com.bumptech.glide.request;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.atLeastOnce;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class RequestFutureTargetTest {\n  private int width;\n  private int height;\n  private RequestFutureTarget<Object> future;\n  private Request request;\n  private RequestFutureTarget.Waiter waiter;\n\n  @Before\n  public void setUp() {\n    width = 100;\n    height = 100;\n    waiter = mock(RequestFutureTarget.Waiter.class);\n    future = new RequestFutureTarget<>(width, height, false, waiter);\n    request = mock(Request.class);\n    future.setRequest(request);\n  }\n\n  @Test\n  public void testCallsSizeReadyCallbackOnGetSize() {\n    SizeReadyCallback cb = mock(SizeReadyCallback.class);\n    future.getSize(cb);\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test\n  public void testReturnsFalseForDoneBeforeDone() {\n    assertFalse(future.isDone());\n  }\n\n  @Test\n  public void testReturnsTrueFromIsDoneIfDone() {\n    future.onResourceReady(\n        /* resource= */ new Object(),\n        /* model= */ null,\n        /* target= */ future,\n        DataSource.DATA_DISK_CACHE,\n        true /*isFirstResource*/);\n    assertTrue(future.isDone());\n  }\n\n  @Test\n  public void testReturnsFalseForIsCancelledBeforeCancelled() {\n    assertFalse(future.isCancelled());\n  }\n\n  @Test\n  public void testReturnsTrueFromCancelIfNotYetDone() {\n    assertTrue(future.cancel(false));\n  }\n\n  @Test\n  public void cancel_withMayInterruptIfRunningTrueAndNotFinishedRequest_clearsFuture() {\n    future.cancel(true);\n\n    verify(request).clear();\n  }\n\n  @Test\n  public void cancel_withInterruptFalseAndNotFinishedRequest_doesNotClearFuture() {\n    future.cancel(false);\n\n    verify(request, never()).clear();\n  }\n\n  @Test\n  public void testDoesNotRepeatedlyClearRequestIfCancelledRepeatedly() {\n    future.cancel(true);\n    future.cancel(true);\n\n    verify(request, times(1)).clear();\n  }\n\n  @Test\n  public void testDoesNotClearRequestIfCancelledAfterDone() {\n    future.onResourceReady(\n        /* resource= */ new Object(),\n        /* model= */ null,\n        /* target= */ future,\n        DataSource.DATA_DISK_CACHE,\n        true /*isFirstResource*/);\n    future.cancel(true);\n\n    verify(request, never()).clear();\n  }\n\n  @Test\n  public void testReturnsTrueFromDoneIfCancelled() {\n    future.cancel(true);\n    assertTrue(future.isDone());\n  }\n\n  @Test\n  public void testReturnsFalseFromIsCancelledIfCancelledAfterDone() {\n    future.onResourceReady(\n        /* resource= */ new Object(),\n        /* model= */ null,\n        /* target= */ future,\n        DataSource.DATA_DISK_CACHE,\n        true /*isFirstResource*/);\n    future.cancel(true);\n\n    assertFalse(future.isCancelled());\n  }\n\n  @Test\n  public void testReturnsTrueFromCancelIfCancelled() {\n    future.cancel(true);\n    assertTrue(future.isCancelled());\n  }\n\n  @Test\n  public void testReturnsFalseFromCancelIfDone() {\n    future.onResourceReady(\n        /* resource= */ new Object(),\n        /* model= */ null,\n        /* target= */ future,\n        DataSource.DATA_DISK_CACHE,\n        true /*isFirstResource*/);\n    assertFalse(future.cancel(true));\n  }\n\n  @Test\n  public void testReturnsResourceOnGetIfAlreadyDone()\n      throws ExecutionException, InterruptedException {\n    Object expected = new Object();\n    future.onResourceReady(\n        /* resource= */ expected,\n        /* model= */ null,\n        /* target= */ future,\n        DataSource.DATA_DISK_CACHE,\n        true /*isFirstResource*/);\n\n    assertEquals(expected, future.get());\n  }\n\n  @Test\n  public void testReturnsResourceOnGetWithTimeoutIfAlreadyDone()\n      throws InterruptedException, ExecutionException, TimeoutException {\n    Object expected = new Object();\n    future.onResourceReady(\n        /* resource= */ expected,\n        /* model= */ null,\n        /* target= */ future,\n        DataSource.DATA_DISK_CACHE,\n        true /*isFirstResource*/);\n\n    assertEquals(expected, future.get(1, TimeUnit.MILLISECONDS));\n  }\n\n  @Test(expected = CancellationException.class)\n  public void testThrowsCancellationExceptionIfCancelledBeforeGet()\n      throws ExecutionException, InterruptedException {\n    future.cancel(true);\n    future.get();\n  }\n\n  @Test(expected = CancellationException.class)\n  public void testThrowsCancellationExceptionIfCancelledBeforeGetWithTimeout()\n      throws InterruptedException, ExecutionException, TimeoutException {\n    future.cancel(true);\n    future.get(100, TimeUnit.MILLISECONDS);\n  }\n\n  @Test(expected = ExecutionException.class)\n  public void testThrowsExecutionExceptionOnGetIfExceptionBeforeGet()\n      throws ExecutionException, InterruptedException {\n    future.onLoadFailed(/* e= */ null, /* model= */ null, future, /* isFirstResource= */ true);\n    future.get();\n  }\n\n  @Test(expected = ExecutionException.class)\n  public void testThrowsExecutionExceptionOnGetIfExceptionWithNullValueBeforeGet()\n      throws ExecutionException, InterruptedException, TimeoutException {\n    future.onLoadFailed(/* e= */ null, /* model= */ null, future, /* isFirstResource= */ true);\n    future.get(100, TimeUnit.MILLISECONDS);\n  }\n\n  @Test(expected = ExecutionException.class)\n  public void testThrowsExecutionExceptionOnGetIfExceptionBeforeGetWithTimeout()\n      throws ExecutionException, InterruptedException, TimeoutException {\n    future.onLoadFailed(/* e= */ null, /* model= */ null, future, /* isFirstResource= */ true);\n    future.get(100, TimeUnit.MILLISECONDS);\n  }\n\n  @Test(expected = TimeoutException.class)\n  public void testThrowsTimeoutExceptionOnGetIfFailedToReceiveResourceInTime()\n      throws InterruptedException, ExecutionException, TimeoutException {\n    future.get(1, TimeUnit.MILLISECONDS);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsExceptionIfGetCalledOnMainThread()\n      throws ExecutionException, InterruptedException {\n    future = new RequestFutureTarget<>(width, height, true, waiter);\n    future.get();\n  }\n\n  @Test\n  public void testGetSucceedsOnMainThreadIfDone() throws ExecutionException, InterruptedException {\n    future = new RequestFutureTarget<>(width, height, true, waiter);\n    future.onResourceReady(\n        /* resource= */ new Object(),\n        /* model= */ null,\n        /* target= */ future,\n        DataSource.DATA_DISK_CACHE,\n        true /*isFirstResource*/);\n    future.get();\n  }\n\n  @Test(expected = InterruptedException.class)\n  public void testThrowsInterruptedExceptionIfThreadInterruptedWhenDoneWaiting()\n      throws InterruptedException, ExecutionException {\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                Thread.currentThread().interrupt();\n                return null;\n              }\n            })\n        .when(waiter)\n        .waitForTimeout(eq(future), anyLong());\n\n    future.get();\n  }\n\n  @Test(expected = ExecutionException.class)\n  public void testThrowsExecutionExceptionIfLoadFailsWhileWaiting()\n      throws ExecutionException, InterruptedException {\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                future.onLoadFailed(\n                    /* e= */ null, /* model= */ null, future, /* isFirstResource= */ true);\n                return null;\n              }\n            })\n        .when(waiter)\n        .waitForTimeout(eq(future), anyLong());\n    future.get();\n  }\n\n  @Test(expected = CancellationException.class)\n  public void testThrowsCancellationExceptionIfCancelledWhileWaiting()\n      throws ExecutionException, InterruptedException {\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                future.cancel(false);\n                return null;\n              }\n            })\n        .when(waiter)\n        .waitForTimeout(eq(future), anyLong());\n    future.get();\n  }\n\n  @Test(expected = TimeoutException.class)\n  public void testThrowsTimeoutExceptionIfFinishesWaitingWithTimeoutAndDoesNotReceiveResult()\n      throws ExecutionException, InterruptedException, TimeoutException {\n    future.get(1, TimeUnit.MILLISECONDS);\n  }\n\n  @Test(expected = AssertionError.class)\n  public void testThrowsAssertionErrorIfFinishesWaitingWithoutTimeoutAndDoesNotReceiveResult()\n      throws ExecutionException, InterruptedException {\n    future.get();\n  }\n\n  @Test\n  public void testNotifiesAllWhenLoadFails() {\n    future.onLoadFailed(/* e= */ null, /* model= */ null, future, /* isFirstResource= */ true);\n    verify(waiter).notifyAll(eq(future));\n  }\n\n  @Test\n  public void testNotifiesAllWhenResourceReady() {\n    future.onResourceReady(\n        /* resource= */ new Object(),\n        /* model= */ null,\n        /* target= */ future,\n        DataSource.DATA_DISK_CACHE,\n        true /*isFirstResource*/);\n    verify(waiter).notifyAll(eq(future));\n  }\n\n  @Test\n  public void testNotifiesAllOnCancelIfNotCancelled() {\n    future.cancel(false);\n    verify(waiter).notifyAll(eq(future));\n  }\n\n  @Test\n  public void testDoesNotNotifyAllOnSecondCancel() {\n    future.cancel(true);\n    verify(waiter).notifyAll(eq(future));\n    future.cancel(true);\n    verify(waiter, times(1)).notifyAll(eq(future));\n  }\n\n  @Test\n  public void testReturnsResourceIfReceivedWhileWaiting()\n      throws ExecutionException, InterruptedException {\n    final Object expected = new Object();\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocationOnMock) {\n                future.onResourceReady(\n                    /* resource= */ expected,\n                    /* model= */ null,\n                    /* target= */ future,\n                    DataSource.DATA_DISK_CACHE,\n                    true /*isFirstResource*/);\n                return null;\n              }\n            })\n        .when(waiter)\n        .waitForTimeout(eq(future), anyLong());\n    assertEquals(expected, future.get());\n  }\n\n  @Test\n  public void testWaitsForeverIfNoTimeoutSet() throws InterruptedException {\n    try {\n      future.get();\n    } catch (ExecutionException e) {\n      throw new RuntimeException(e);\n    } catch (AssertionError e) {\n      // Expected.\n    }\n    verify(waiter).waitForTimeout(eq(future), eq(0L));\n  }\n\n  @Test\n  public void testWaitsForGivenTimeoutMillisIfTimeoutSet() throws InterruptedException {\n    long timeout = 2;\n    try {\n      future.get(timeout, TimeUnit.MILLISECONDS);\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    } catch (TimeoutException e) {\n      // Expected.\n    }\n\n    verify(waiter, atLeastOnce()).waitForTimeout(eq(future), eq(timeout));\n  }\n\n  @Test\n  public void testConvertsOtherTimeUnitsToMillisForWaiter() throws InterruptedException {\n    long timeoutMicros = 1000;\n    try {\n      future.get(timeoutMicros, TimeUnit.MICROSECONDS);\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    } catch (TimeoutException e) {\n      // Expected.\n    }\n\n    verify(waiter, atLeastOnce())\n        .waitForTimeout(eq(future), eq(TimeUnit.MICROSECONDS.toMillis(timeoutMicros)));\n  }\n\n  @Test\n  public void testDoesNotWaitIfGivenTimeOutEqualToZero() throws InterruptedException {\n    try {\n      future.get(0, TimeUnit.MILLISECONDS);\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    } catch (TimeoutException e) {\n      // Expected.\n    }\n\n    verify(waiter, never()).waitForTimeout(eq(future), anyLong());\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/RequestOptionsTest.java",
    "content": "package com.bumptech.glide.request;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.app.Application;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.GradientDrawable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.MultiTransformation;\nimport com.bumptech.glide.load.Option;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop;\nimport com.bumptech.glide.load.resource.bitmap.CircleCrop;\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.bumptech.glide.util.Util;\nimport com.google.common.testing.EqualsTester;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class RequestOptionsTest {\n\n  private RequestOptions options;\n  @Mock private Transformation<Bitmap> transformation;\n  private Application app;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    options = new RequestOptions();\n\n    app = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_byDefault_isTrue() {\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withFitCenter_isTrue() {\n    options.fitCenter();\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n    options.optionalFitCenter();\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withCenterInside_isTrue() {\n    options.centerInside();\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n    options.optionalCenterInside();\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withCenterCrop_isFalse() {\n    options.centerCrop();\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n    options.optionalCenterCrop();\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withCircleCrop_isFalse() {\n    options.circleCrop();\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n    options.circleCrop();\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withBitmapTransformation_isFalse() {\n    options.transform(transformation);\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n    options.optionalTransform(transformation);\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withCustomTransformation_isFalse() {\n    options.transform(Bitmap.class, transformation);\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n    options.optionalTransform(Bitmap.class, transformation);\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withDownsampleStrategy_isTrue() {\n    options.downsample(DownsampleStrategy.CENTER_OUTSIDE);\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withNonScaleAndThenDontTransform_isTrue() {\n    options.circleCrop().dontTransform();\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withNonScaleAndAppliedDontTransform_isTrue() {\n    options.circleCrop();\n    options.apply(new RequestOptions().dontTransform());\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withDontTransformAndAppliedNonScaleTransform_isFalse() {\n    options.fitCenter();\n    options.apply(new RequestOptions().circleCrop());\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withNonScaleOnly_andAppliedWithScaleOnly_isTrue() {\n    options.circleCrop();\n    options.apply(new RequestOptions().fitCenter());\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withScaleOnlyAndAppliedWithoutTransform_isTrue() {\n    options.fitCenter();\n    options.apply(new RequestOptions().dontAnimate());\n    assertThat(options.isScaleOnlyOrNoTransform()).isTrue();\n  }\n\n  @Test\n  public void isScaleOnlyOrNoTransform_withNonScaleOnlyAndAppliedWithoutTransform_isFalse() {\n    options.circleCrop();\n    options.apply(new RequestOptions().dontAnimate());\n    assertThat(options.isScaleOnlyOrNoTransform()).isFalse();\n  }\n\n  @Test\n  public void testIsTransformationRequired_byDefault_isFalse() {\n    assertThat(options.isTransformationRequired()).isFalse();\n  }\n\n  @Test\n  public void testIsTransformationSet_byDefault_isFalse() {\n    assertThat(options.isTransformationSet()).isFalse();\n  }\n\n  @Test\n  public void testIsTransformationAllowed_byDefault_isTrue() {\n    assertThat(options.isTransformationAllowed()).isTrue();\n  }\n\n  @Test\n  public void testIsTransformationSet_afterApplyingOptionsWithTransform_isTrue() {\n    RequestOptions other = new RequestOptions();\n    other.transform(Bitmap.class, transformation);\n    options.apply(other);\n    assertThat(options.isTransformationSet()).isTrue();\n  }\n\n  @Test\n  public void testIsTransformationSet_afterDontTransform_isFalse() {\n    options.dontTransform();\n    assertThat(options.isTransformationSet()).isFalse();\n  }\n\n  @Test\n  public void testIsTransformationAllowed_afterDontTransform_isFalse() {\n    options.dontTransform();\n    assertThat(options.isTransformationAllowed()).isFalse();\n  }\n\n  @Test\n  public void testIsTransformationRequired_afterDontTransform_isFalse() {\n    options.dontTransform();\n    assertThat(options.isTransformationRequired()).isFalse();\n  }\n\n  @Test\n  public void testApplyingDontTransform_overridesTransformations() {\n    options.transform(transformation);\n    options.dontTransform();\n    assertThat(options.isTransformationSet()).isFalse();\n    assertThat(options.isTransformationRequired()).isFalse();\n    assertThat(options.getTransformations()).isEmpty();\n  }\n\n  @Test\n  public void testApplyingTransformation_overridesDontTransform() {\n    options.dontTransform();\n    options.transform(transformation);\n\n    assertThat(options.isTransformationAllowed()).isTrue();\n    assertThat(options.isTransformationRequired()).isTrue();\n    assertThat(options.getTransformations()).containsEntry(Bitmap.class, transformation);\n  }\n\n  @Test\n  public void testApplyingOptions_withDontTransform_overridesTransformations() {\n    options.transform(transformation);\n    RequestOptions other = new RequestOptions();\n    other.dontTransform();\n\n    options.apply(other);\n\n    assertThat(options.isTransformationAllowed()).isFalse();\n    assertThat(options.isTransformationSet()).isFalse();\n    assertThat(options.isTransformationRequired()).isFalse();\n    assertThat(options.getTransformations()).isEmpty();\n  }\n\n  @Test\n  public void testApplyingOptions_withTransformation_overridesDontTransform() {\n    options.dontTransform();\n    RequestOptions other = new RequestOptions();\n    other.transform(transformation);\n\n    options.apply(other);\n\n    assertThat(options.isTransformationAllowed()).isTrue();\n    assertThat(options.isTransformationSet()).isTrue();\n    assertThat(options.isTransformationRequired()).isTrue();\n    assertThat(options.getTransformations()).containsEntry(Bitmap.class, transformation);\n  }\n\n  @Test\n  public void testApplyingDefaultOptions_withDontTransform_retainsDontTransform() {\n    options.dontTransform();\n    options.apply(new RequestOptions());\n\n    assertThat(options.isTransformationAllowed()).isFalse();\n    assertThat(options.isTransformationRequired()).isFalse();\n    assertThat(options.getTransformations()).isEmpty();\n  }\n\n  @Test\n  public void testApplyingDefaultOptions_withTransform_retrainsTransform() {\n    options.transform(transformation);\n    options.apply(new RequestOptions());\n\n    assertThat(options.isTransformationAllowed()).isTrue();\n    assertThat(options.isTransformationRequired()).isTrue();\n    assertThat(options.getTransformations()).containsEntry(Bitmap.class, transformation);\n  }\n\n  @Test\n  @SuppressWarnings({\"unchecked\", \"varargs\"})\n  public void testApplyMultiTransform() {\n    options.transform(new CircleCrop(), new CenterCrop());\n    assertThat(options.isTransformationRequired()).isTrue();\n    assertThat(options.getTransformations()).containsKey(Bitmap.class);\n    assertThat(options.getTransformations().get(Bitmap.class))\n        .isInstanceOf(MultiTransformation.class);\n  }\n\n  @Test\n  public void isSkipMemoryCacheSet_withoutSkipMemoryCache_isFalse() {\n    assertThat(options.isSkipMemoryCacheSet()).isFalse();\n  }\n\n  @Test\n  public void isSkipMemoryCacheSet_withSkipMemoryCacheTrue_isTrue() {\n    assertThat(options.skipMemoryCache(true).isSkipMemoryCacheSet()).isTrue();\n  }\n\n  @Test\n  public void isSkipMemoryCacheSet_withSkipMemoryCacheFalse_isTrue() {\n    assertThat(options.skipMemoryCache(false).isSkipMemoryCacheSet()).isTrue();\n  }\n\n  @Test\n  public void isDiskCacheStrategySet_withoutDiskCacheStrategy_isFalse() {\n    assertThat(options.isDiskCacheStrategySet()).isFalse();\n  }\n\n  @Test\n  public void isDiskCacheStrategySet_withDiskCacheStrategyDefault_isTrue() {\n    assertThat(options.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).isDiskCacheStrategySet())\n        .isTrue();\n  }\n\n  @Test\n  public void isDiskCacheStrategySet_withDiskCacheStrategyNonDefault_isTrue() {\n    assertThat(options.diskCacheStrategy(DiskCacheStrategy.ALL).isDiskCacheStrategySet()).isTrue();\n  }\n\n  @Test\n  public void getPlaceholder_afterSettingPlaceholderId_returnsNul() {\n    assertThat(\n            options\n                .placeholder(new ColorDrawable(Color.RED))\n                .placeholder(android.R.drawable.star_on)\n                .getPlaceholderDrawable())\n        .isNull();\n  }\n\n  @Test\n  public void getPlaceholder_afterApplyingOptionsWithPlaceholderId_returnsNull() {\n    RequestOptions toApply = new RequestOptions().placeholder(android.R.drawable.star_on);\n\n    assertThat(\n            options\n                .placeholder(new ColorDrawable(Color.RED))\n                .apply(toApply)\n                .getPlaceholderDrawable())\n        .isNull();\n  }\n\n  @Test\n  public void getPlaceholder_afterApplyingOptionsWithPlaceholderDrawable_returnsNewDrawable() {\n    Drawable expected = new ColorDrawable(Color.GREEN);\n    RequestOptions toApply = new RequestOptions().placeholder(expected);\n\n    assertThat(\n            options\n                .placeholder(new ColorDrawable(Color.RED))\n                .apply(toApply)\n                .getPlaceholderDrawable())\n        .isEqualTo(expected);\n  }\n\n  /**\n   * Verifies that we set the flags for placeholder id correctly when applying a placeholder id via\n   * another RequestOptions.\n   */\n  @Test\n  public void placeholderIdFlag_afterApplyingIdViaOtherRequestOptions_isSet() {\n    assertThat(\n            options\n                .placeholder(new ColorDrawable(Color.RED))\n                .apply(\n                    new RequestOptions()\n                        .apply(new RequestOptions().placeholder(android.R.drawable.star_on)))\n                .getPlaceholderDrawable())\n        .isNull();\n  }\n\n  @Test\n  public void getPlaceholderId_afterSettingPlaceholderDrawable_returnsZero() {\n    assertThat(\n            options\n                .placeholder(android.R.drawable.star_on)\n                .placeholder(new ColorDrawable(Color.RED))\n                .getPlaceholderId())\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void getPlaceholderId_afterApplyingOptionsWithPlaceholderDrawable_returnsZero() {\n    RequestOptions toApply = new RequestOptions().placeholder(new ColorDrawable(Color.RED));\n\n    assertThat(options.placeholder(android.R.drawable.star_on).apply(toApply).getPlaceholderId())\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void getPlaceholderId_afterApplyingOptionsWithId_returnsNewId() {\n    int expectedId = android.R.drawable.star_off;\n    RequestOptions toApply = new RequestOptions().placeholder(expectedId);\n\n    assertThat(options.placeholder(android.R.drawable.star_on).apply(toApply).getPlaceholderId())\n        .isEqualTo(expectedId);\n  }\n\n  /**\n   * Verifies that we set the flags for placeholder correctly when applying a placeholder via\n   * another RequestOptions.\n   */\n  @Test\n  public void placeholderFlag_afterApplyingViaOtherRequestOptions_isSet() {\n    assertThat(\n            options\n                .placeholder(android.R.drawable.star_on)\n                .apply(\n                    new RequestOptions()\n                        .apply(new RequestOptions().placeholder(new ColorDrawable(Color.RED))))\n                .getPlaceholderId())\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void getFallback_afterSettingFallbackId_returnsNull() {\n    assertThat(\n            options\n                .fallback(new ColorDrawable(Color.RED))\n                .fallback(android.R.drawable.star_on)\n                .getFallbackDrawable())\n        .isNull();\n  }\n\n  @Test\n  public void getFallback_afterApplyingOptionsWithFallbackId_returnsNull() {\n    RequestOptions toApply = new RequestOptions().fallback(android.R.drawable.star_on);\n\n    assertThat(options.fallback(new ColorDrawable(Color.RED)).apply(toApply).getFallbackDrawable())\n        .isNull();\n  }\n\n  @Test\n  public void getFallback_afterApplyingOptionsWithFallbackDrawable_returnsNewDrawable() {\n    RequestOptions toApply = new RequestOptions();\n\n    RequestOptions apply = options.fallback(new ColorDrawable(Color.RED)).apply(toApply);\n    assertThat(((ColorDrawable) apply.getFallbackDrawable()).getColor()).isEqualTo(Color.RED);\n  }\n\n  /**\n   * Verifies that we set the flags for fallback correctly when applying a fallback via another\n   * RequestOptions.\n   */\n  @Test\n  public void fallbackFlag_afterApplyingViaOtherRequestOptions_isSet() {\n    assertThat(\n            options\n                .fallback(android.R.drawable.star_on)\n                .apply(\n                    new RequestOptions()\n                        .apply(new RequestOptions().fallback(new ColorDrawable(Color.RED))))\n                .getFallbackId())\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void getFallbackId_afterSettingFallbackDrawable_returnsZero() {\n    assertThat(\n            options\n                .fallback(android.R.drawable.star_on)\n                .fallback(new ColorDrawable(Color.RED))\n                .getFallbackId())\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void getFallbackId_afterApplyingOptionsWithFallbackDrawable_returnsZero() {\n    RequestOptions toApply = new RequestOptions().fallback(new ColorDrawable(Color.RED));\n\n    assertThat(options.fallback(android.R.drawable.star_on).apply(toApply).getFallbackId())\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void getFallbackId_afterApplyingOptionsWithFallbackId_returnsNewFallbackId() {\n    RequestOptions toApply = new RequestOptions().fallback(android.R.drawable.star_off);\n\n    assertThat(options.fallback(android.R.drawable.star_on).apply(toApply).getFallbackId())\n        .isEqualTo(android.R.drawable.star_off);\n  }\n\n  /**\n   * Verifies that we set the flags for fallback id correctly when applying a fallback id via\n   * another RequestOptions.\n   */\n  @Test\n  public void fallbackIdFlag_afterApplyingViaOtherRequestOptions_isSet() {\n    assertThat(\n            options\n                .fallback(new ColorDrawable(Color.RED))\n                .apply(\n                    new RequestOptions()\n                        .apply(new RequestOptions().fallback(android.R.drawable.star_on)))\n                .getFallbackDrawable())\n        .isNull();\n  }\n\n  @Test\n  public void getError_afterSettingErrorId_returnsNull() {\n    assertThat(\n            options\n                .error(new ColorDrawable(Color.RED))\n                .error(android.R.drawable.star_on)\n                .getErrorPlaceholder())\n        .isNull();\n  }\n\n  @Test\n  public void getError_afterApplyingOptionsWithErrorId_returnsNull() {\n    RequestOptions toApply = new RequestOptions().error(android.R.drawable.star_on);\n\n    assertThat(options.error(new ColorDrawable(Color.RED)).apply(toApply).getErrorPlaceholder())\n        .isNull();\n  }\n\n  @Test\n  public void getError_afterApplyingOptionsWithErrorDrawable_returnsNewErrorDrawable() {\n    Drawable expected = new ColorDrawable(Color.GREEN);\n    RequestOptions toApply = new RequestOptions().error(expected);\n\n    assertThat(options.error(new ColorDrawable(Color.RED)).apply(toApply).getErrorPlaceholder())\n        .isEqualTo(expected);\n  }\n\n  /**\n   * Verifies that we set the flags for error correctly when applying an error via another\n   * RequestOptions.\n   */\n  @Test\n  public void errorFlag_afterApplyingViaOtherRequestOptions_isSet() {\n    assertThat(\n            options\n                .error(android.R.drawable.star_on)\n                .apply(\n                    new RequestOptions()\n                        .apply(new RequestOptions().error(new ColorDrawable(Color.RED))))\n                .getErrorId())\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void getErrorId_afterSettingErrorDrawable_returnsZero() {\n    assertThat(\n            options\n                .error(android.R.drawable.star_on)\n                .error(new ColorDrawable(Color.RED))\n                .getErrorId())\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void getErrorId_afterApplyingOptionsWithErrorDrawable_returnsZero() {\n    RequestOptions toApply = new RequestOptions().error(new ColorDrawable(Color.RED));\n\n    assertThat(options.error(android.R.drawable.star_on).apply(toApply).getErrorId()).isEqualTo(0);\n  }\n\n  @Test\n  public void getErrorId_afterApplyingOptionsWithErrorId_returnsNewErrorId() {\n    RequestOptions toApply = new RequestOptions().error(android.R.drawable.star_off);\n\n    assertThat(options.error(android.R.drawable.star_on).apply(toApply).getErrorId())\n        .isEqualTo(android.R.drawable.star_off);\n  }\n\n  /**\n   * Verifies that we set the flags for error id correctly when applying a fallback id via another\n   * RequestOptions.\n   */\n  @Test\n  public void errorIdFlag_afterApplyingViaOtherRequestOptions_isSet() {\n    assertThat(\n            options\n                .error(new ColorDrawable(Color.RED))\n                .apply(\n                    new RequestOptions()\n                        .apply(new RequestOptions().error(android.R.drawable.star_on)))\n                .getErrorPlaceholder())\n        .isNull();\n  }\n\n  @Test\n  public void testEqualsHashCode() {\n    Drawable first = new ColorDrawable(Color.RED);\n    Drawable second = new GradientDrawable();\n    assertThat(first).isNotEqualTo(second);\n    assertThat(Util.bothNullOrEqual(first, second)).isFalse();\n    // Make sure we're not equal to any other subclass of RequestOptions.\n    class FakeOptions extends BaseRequestOptions<FakeOptions> {\n      @Override\n      public boolean equals(Object o) {\n        return o instanceof FakeOptions && super.equals(o);\n      }\n\n      // Our class doesn't include any additional properties, so we don't need to modify hashcode,\n      // but\n      // keep it here as a reminder in case we add properties.\n      @SuppressWarnings(\"PMD.UselessOverridingMethod\")\n      @Override\n      public int hashCode() {\n        return super.hashCode();\n      }\n    }\n    new EqualsTester()\n        .addEqualityGroup(\n            new RequestOptions(),\n            new RequestOptions(),\n            new RequestOptions().skipMemoryCache(false),\n            new RequestOptions().onlyRetrieveFromCache(false),\n            new RequestOptions().useUnlimitedSourceGeneratorsPool(false))\n        .addEqualityGroup(new FakeOptions(), new FakeOptions())\n        .addEqualityGroup(\n            new RequestOptions().sizeMultiplier(.7f), new RequestOptions().sizeMultiplier(.7f))\n        .addEqualityGroup(new RequestOptions().sizeMultiplier(0.8f))\n        .addEqualityGroup(new RequestOptions().error(1), new RequestOptions().error(1))\n        .addEqualityGroup(new RequestOptions().error(2))\n        .addEqualityGroup(new RequestOptions().error(first), new RequestOptions().error(first))\n        .addEqualityGroup(new RequestOptions().error(second))\n        .addEqualityGroup(new RequestOptions().placeholder(1), new RequestOptions().placeholder(1))\n        .addEqualityGroup(new RequestOptions().placeholder(2))\n        .addEqualityGroup(\n            new RequestOptions().placeholder(first), new RequestOptions().placeholder(first))\n        .addEqualityGroup(new RequestOptions().placeholder(second))\n        .addEqualityGroup(new RequestOptions().fallback(1), new RequestOptions().fallback(1))\n        .addEqualityGroup(new RequestOptions().fallback(2))\n        .addEqualityGroup(\n            new RequestOptions().fallback(first), new RequestOptions().fallback(first))\n        .addEqualityGroup(new RequestOptions().fallback(second))\n        .addEqualityGroup(\n            new RequestOptions().skipMemoryCache(true), new RequestOptions().skipMemoryCache(true))\n        .addEqualityGroup(\n            new RequestOptions().override(100), new RequestOptions().override(100, 100))\n        .addEqualityGroup(\n            new RequestOptions().override(200), new RequestOptions().override(200, 200))\n        .addEqualityGroup(\n            new RequestOptions().override(100, 200), new RequestOptions().override(100, 200))\n        .addEqualityGroup(\n            new RequestOptions().override(200, 100), new RequestOptions().override(200, 100))\n        .addEqualityGroup(new RequestOptions().centerCrop(), new RequestOptions().centerCrop())\n        .addEqualityGroup(\n            new RequestOptions().optionalCenterCrop(), new RequestOptions().optionalCenterCrop())\n        .addEqualityGroup(new RequestOptions().fitCenter())\n        .addEqualityGroup(new RequestOptions().circleCrop())\n        .addEqualityGroup(new RequestOptions().centerInside())\n        .addEqualityGroup(\n            new RequestOptions().useUnlimitedSourceGeneratorsPool(true),\n            new RequestOptions().useUnlimitedSourceGeneratorsPool(true))\n        .addEqualityGroup(\n            new RequestOptions().onlyRetrieveFromCache(true),\n            new RequestOptions().onlyRetrieveFromCache(true))\n        .addEqualityGroup(\n            new RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL),\n            new RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL))\n        .addEqualityGroup(new RequestOptions().diskCacheStrategy(DiskCacheStrategy.NONE))\n        .addEqualityGroup(\n            new RequestOptions().priority(Priority.HIGH),\n            new RequestOptions().priority(Priority.HIGH))\n        .addEqualityGroup(new RequestOptions().priority(Priority.LOW))\n        .addEqualityGroup(\n            new RequestOptions().set(Option.memory(\"test\"), true),\n            new RequestOptions().set(Option.memory(\"test\"), true))\n        .addEqualityGroup(new RequestOptions().set(Option.memory(\"test\"), false))\n        .addEqualityGroup(new RequestOptions().set(Option.memory(\"test2\"), true))\n        .addEqualityGroup(\n            new RequestOptions().decode(Integer.class), new RequestOptions().decode(Integer.class))\n        .addEqualityGroup(new RequestOptions().decode(Float.class))\n        .addEqualityGroup(\n            new RequestOptions().signature(new ObjectKey(\"test\")),\n            new RequestOptions().signature(new ObjectKey(\"test\")))\n        .addEqualityGroup(new RequestOptions().signature(new ObjectKey(\"test2\")))\n        .addEqualityGroup(\n            new RequestOptions().theme(app.getTheme()), new RequestOptions().theme(app.getTheme()))\n        .testEquals();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/SingleRequestTest.java",
    "content": "package com.bumptech.glide.request;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.bumptech.glide.tests.Util.isADataSource;\nimport static com.bumptech.glide.tests.Util.mockResource;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.GlideContext;\nimport com.bumptech.glide.GlideExperiments;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.Transformation;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.load.engine.Engine;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.request.target.CustomTarget;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.request.transition.TransitionFactory;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.bumptech.glide.util.Executors;\nimport com.google.common.base.Equivalence;\nimport com.google.common.testing.EquivalenceTester;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\n@SuppressWarnings(\"rawtypes\")\npublic class SingleRequestTest {\n\n  private SingleRequestBuilder builder;\n  @Mock private ExperimentalRequestListener<List> listener1;\n  @Mock private RequestListener<List> listener2;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    builder = new SingleRequestBuilder();\n  }\n\n  @Test\n  public void testIsNotCompleteBeforeReceivingResource() {\n    SingleRequest<List> request = builder.build();\n\n    assertFalse(request.isComplete());\n  }\n\n  @Test\n  public void testCanHandleNullResources() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n\n    request.onResourceReady(null, DataSource.LOCAL, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onLoadFailed(isAGlideException(), isA(Number.class), eq(builder.target), anyBoolean());\n  }\n\n  @Test\n  public void testCanHandleEmptyResources() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    when(builder.resource.get()).thenReturn(null);\n\n    request.onResourceReady(\n        builder.resource, DataSource.REMOTE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(builder.engine).release(eq(builder.resource));\n    verify(listener1)\n        .onLoadFailed(isAGlideException(), any(Number.class), eq(builder.target), anyBoolean());\n  }\n\n  @Test\n  public void testCanHandleNonConformingResources() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    when(((Resource) (builder.resource)).get())\n        .thenReturn(\"Invalid mocked String, this should be a List\");\n\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ true);\n\n    verify(builder.engine).release(eq(builder.resource));\n    verify(listener1)\n        .onLoadFailed(isAGlideException(), any(Number.class), eq(builder.target), anyBoolean());\n  }\n\n  @Test\n  public void testIsCompleteAfterReceivingResource() {\n    SingleRequest<List> request = builder.build();\n\n    request.onResourceReady(\n        builder.resource, DataSource.LOCAL, /* isLoadedFromAlternateCacheKey= */ false);\n\n    assertTrue(request.isComplete());\n  }\n\n  @Test\n  public void testIsNotCompleteAfterClear() {\n    SingleRequest<List> request = builder.build();\n    request.onResourceReady(\n        builder.resource, DataSource.REMOTE, /* isLoadedFromAlternateCacheKey= */ false);\n    request.clear();\n\n    assertFalse(request.isComplete());\n  }\n\n  @Test\n  public void testIsCancelledAfterClear() {\n    SingleRequest<List> request = builder.build();\n    request.clear();\n\n    assertTrue(request.isCleared());\n  }\n\n  @Test\n  public void clear_notifiesTarget() {\n    SingleRequest<List> request = builder.build();\n    request.clear();\n\n    verify(builder.target).onLoadCleared(anyDrawableOrNull());\n  }\n\n  @Test\n  public void testDoesNotNotifyTargetTwiceIfClearedTwiceInARow() {\n    SingleRequest<List> request = builder.build();\n    request.clear();\n    request.clear();\n\n    verify(builder.target, times(1)).onLoadCleared(anyDrawableOrNull());\n  }\n\n  @Test\n  public void clear_doesNotNotifyTarget_ifRequestCoordinatorReturnsFalseForCanClear() {\n    when(builder.requestCoordinator.canNotifyCleared(any(Request.class))).thenReturn(false);\n    SingleRequest<List> request = builder.build();\n    request.clear();\n\n    verify(builder.target, never()).onLoadCleared(any(Drawable.class));\n  }\n\n  @Test\n  public void testResourceIsNotCompleteWhenAskingCoordinatorIfCanSetImage() {\n    RequestCoordinator requestCoordinator = mock(RequestCoordinator.class);\n    when(requestCoordinator.getRoot()).thenReturn(requestCoordinator);\n    doAnswer(\n            new Answer() {\n              @Override\n              public Object answer(InvocationOnMock invocation) {\n                Request request = (Request) invocation.getArguments()[0];\n                assertFalse(request.isComplete());\n                return true;\n              }\n            })\n        .when(requestCoordinator)\n        .canSetImage(any(Request.class));\n\n    SingleRequest<List> request = builder.setRequestCoordinator(requestCoordinator).build();\n\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(requestCoordinator).canSetImage(eq(request));\n  }\n\n  @Test\n  public void pause_whenRequestIsWaitingForASize_clearsRequest() {\n    SingleRequest<List> request = builder.build();\n\n    request.begin();\n    request.pause();\n    assertThat(request.isRunning()).isFalse();\n    assertThat(request.isCleared()).isTrue();\n  }\n\n  @Test\n  public void pause_whenRequestIsWaitingForAResource_clearsRequest() {\n    SingleRequest<List> request = builder.build();\n\n    request.begin();\n    request.onSizeReady(100, 100);\n    request.pause();\n    assertThat(request.isRunning()).isFalse();\n    assertThat(request.isCleared()).isTrue();\n  }\n\n  @Test\n  public void pause_whenComplete_doesNotClearRequest() {\n    SingleRequest<List> request = builder.build();\n\n    request.onResourceReady(\n        builder.resource, DataSource.REMOTE, /* isLoadedFromAlternateCacheKey= */ false);\n    request.pause();\n    assertThat(request.isComplete()).isTrue();\n  }\n\n  @Test\n  public void pause_whenCleared_doesNotClearRequest() {\n    SingleRequest<List> request = builder.build();\n\n    request.clear();\n    request.pause();\n\n    verify(builder.target, times(1)).onLoadCleared(anyDrawableOrNull());\n  }\n\n  @Test\n  public void testIgnoresOnSizeReadyIfNotWaitingForSize() {\n    SingleRequest<List> request = builder.build();\n    request.begin();\n    request.onSizeReady(100, 100);\n    request.onSizeReady(100, 100);\n\n    verify(builder.engine, times(1))\n        .load(\n            eq(builder.glideContext),\n            eq(builder.model),\n            eq(builder.signature),\n            eq(100),\n            eq(100),\n            eq(Object.class),\n            eq(List.class),\n            any(Priority.class),\n            any(DiskCacheStrategy.class),\n            eq(builder.transformations),\n            anyBoolean(),\n            anyBoolean(),\n            any(Options.class),\n            anyBoolean(),\n            anyBoolean(),\n            /* useAnimationPool= */ anyBoolean(),\n            anyBoolean(),\n            any(ResourceCallback.class),\n            anyExecutor());\n  }\n\n  @Test\n  public void testEngineLoadCancelledOnCancel() {\n    Engine.LoadStatus loadStatus = mock(Engine.LoadStatus.class);\n\n    when(builder.engine.load(\n            eq(builder.glideContext),\n            eq(builder.model),\n            eq(builder.signature),\n            anyInt(),\n            anyInt(),\n            eq(Object.class),\n            eq(List.class),\n            any(Priority.class),\n            any(DiskCacheStrategy.class),\n            eq(builder.transformations),\n            anyBoolean(),\n            anyBoolean(),\n            any(Options.class),\n            anyBoolean(),\n            anyBoolean(),\n            anyBoolean(),\n            anyBoolean(),\n            any(ResourceCallback.class),\n            anyExecutor()))\n        .thenReturn(loadStatus);\n\n    SingleRequest<List> request = builder.build();\n    request.begin();\n\n    request.onSizeReady(100, 100);\n    request.clear();\n\n    verify(loadStatus).cancel();\n  }\n\n  @Test\n  public void testResourceIsRecycledOnClear() {\n    SingleRequest<List> request = builder.build();\n\n    request.onResourceReady(\n        builder.resource, DataSource.REMOTE, /* isLoadedFromAlternateCacheKey= */ false);\n    request.clear();\n\n    verify(builder.engine).release(eq(builder.resource));\n  }\n\n  @Test\n  public void testPlaceholderDrawableIsSet() {\n    Drawable expected = new ColorDrawable(Color.RED);\n\n    MockTarget target = new MockTarget();\n\n    SingleRequest<List> request =\n        builder.setPlaceholderDrawable(expected).setTarget(target).build();\n    request.begin();\n\n    assertThat(target.currentPlaceholder).isEqualTo(expected);\n  }\n\n  @Test\n  public void testErrorDrawableIsSetOnLoadFailed() {\n    Drawable expected = new ColorDrawable(Color.RED);\n\n    MockTarget target = new MockTarget();\n\n    SingleRequest<List> request = builder.setErrorDrawable(expected).setTarget(target).build();\n\n    request.onLoadFailed(new GlideException(\"test\"));\n\n    assertThat(target.currentPlaceholder).isEqualTo(expected);\n  }\n\n  @Test\n  public void testPlaceholderDrawableSetOnNullModelWithNoErrorDrawable() {\n    Drawable placeholder = new ColorDrawable(Color.RED);\n\n    MockTarget target = new MockTarget();\n\n    SingleRequest<List> request =\n        builder.setErrorDrawable(placeholder).setTarget(target).setModel(null).build();\n\n    request.begin();\n\n    assertThat(target.currentPlaceholder).isEqualTo(placeholder);\n  }\n\n  @Test\n  public void testErrorDrawableSetOnNullModelWithErrorDrawable() {\n    Drawable placeholder = new ColorDrawable(Color.RED);\n    Drawable errorPlaceholder = new ColorDrawable(Color.GREEN);\n\n    MockTarget target = new MockTarget();\n\n    SingleRequest<List> request =\n        builder\n            .setPlaceholderDrawable(placeholder)\n            .setErrorDrawable(errorPlaceholder)\n            .setTarget(target)\n            .setModel(null)\n            .build();\n\n    request.begin();\n\n    assertThat(target.currentPlaceholder).isEqualTo(errorPlaceholder);\n  }\n\n  @Test\n  public void testFallbackDrawableSetOnNullModelWithErrorAndFallbackDrawables() {\n    Drawable placeholder = new ColorDrawable(Color.RED);\n    Drawable errorPlaceholder = new ColorDrawable(Color.GREEN);\n    Drawable fallback = new ColorDrawable(Color.BLUE);\n\n    MockTarget target = new MockTarget();\n    SingleRequest<List> request =\n        builder\n            .setPlaceholderDrawable(placeholder)\n            .setErrorDrawable(errorPlaceholder)\n            .setFallbackDrawable(fallback)\n            .setTarget(target)\n            .setModel(null)\n            .build();\n    request.begin();\n\n    assertThat(target.currentPlaceholder).isEqualTo(fallback);\n  }\n\n  @Test\n  public void testIsNotRunningBeforeRunCalled() {\n    assertFalse(builder.build().isRunning());\n  }\n\n  @Test\n  public void testIsRunningAfterRunCalled() {\n    Request request = builder.build();\n    request.begin();\n    assertTrue(request.isRunning());\n  }\n\n  @Test\n  public void testIsNotRunningAfterComplete() {\n    SingleRequest<List> request = builder.build();\n    request.begin();\n    request.onResourceReady(\n        builder.resource, DataSource.REMOTE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    assertFalse(request.isRunning());\n  }\n\n  @Test\n  public void testIsNotRunningAfterFailing() {\n    SingleRequest<List> request = builder.build();\n    request.begin();\n    request.onLoadFailed(new GlideException(\"test\"));\n\n    assertFalse(request.isRunning());\n  }\n\n  @Test\n  public void testIsNotRunningAfterClear() {\n    SingleRequest<List> request = builder.build();\n    request.begin();\n    request.clear();\n\n    assertFalse(request.isRunning());\n  }\n\n  @Test\n  public void testCallsTargetOnResourceReadyIfNoRequestListener() {\n    SingleRequest<List> request = builder.build();\n    request.onResourceReady(\n        builder.resource, DataSource.LOCAL, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(builder.target).onResourceReady(eq(builder.result), anyTransition());\n  }\n\n  @Test\n  public void testCallsTargetOnResourceReadyIfAllRequestListenersReturnFalse() {\n    SingleRequest<List> request =\n        builder.addRequestListener(listener1).addRequestListener(listener2).build();\n\n    when(listener1.onResourceReady(\n            any(List.class), any(Number.class), eq(builder.target), isADataSource(), anyBoolean()))\n        .thenReturn(false);\n    when(listener2.onResourceReady(\n            any(List.class), any(Number.class), eq(builder.target), isADataSource(), anyBoolean()))\n        .thenReturn(false);\n    request.onResourceReady(\n        builder.resource, DataSource.LOCAL, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(builder.target).onResourceReady(eq(builder.result), anyTransition());\n  }\n\n  @Test\n  public void testDoesNotCallTargetOnResourceReadyIfAnyRequestListenerReturnsTrue() {\n    SingleRequest<List> request =\n        builder.addRequestListener(listener1).addRequestListener(listener2).build();\n\n    when(listener1.onResourceReady(\n            any(List.class), any(Number.class), eq(builder.target), isADataSource(), anyBoolean()))\n        .thenReturn(false);\n    when(listener1.onResourceReady(\n            any(List.class), any(Number.class), eq(builder.target), isADataSource(), anyBoolean()))\n        .thenReturn(true);\n    request.onResourceReady(\n        builder.resource, DataSource.REMOTE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(builder.target, never()).onResourceReady(any(List.class), anyTransition());\n  }\n\n  @Test\n  public void testCallsTargetOnExceptionIfNoRequestListener() {\n    SingleRequest<List> request = builder.build();\n    request.onLoadFailed(new GlideException(\"test\"));\n\n    verify(builder.target).onLoadFailed(eq(builder.errorDrawable));\n  }\n\n  @Test\n  public void testCallsTargetOnExceptionIfAllRequestListenersReturnFalse() {\n    SingleRequest<List> request =\n        builder.addRequestListener(listener1).addRequestListener(listener2).build();\n\n    when(listener1.onLoadFailed(\n            isAGlideException(), any(Number.class), eq(builder.target), anyBoolean()))\n        .thenReturn(false);\n    when(listener2.onLoadFailed(\n            isAGlideException(), any(Number.class), eq(builder.target), anyBoolean()))\n        .thenReturn(false);\n    request.onLoadFailed(new GlideException(\"test\"));\n\n    verify(builder.target).onLoadFailed(eq(builder.errorDrawable));\n  }\n\n  @Test\n  public void testDoesNotCallTargetOnExceptionIfAnyRequestListenerReturnsTrue() {\n    SingleRequest<List> request =\n        builder.addRequestListener(listener1).addRequestListener(listener2).build();\n\n    when(listener1.onLoadFailed(\n            isAGlideException(), any(Number.class), eq(builder.target), anyBoolean()))\n        .thenReturn(false);\n    when(listener2.onLoadFailed(\n            isAGlideException(), any(Number.class), eq(builder.target), anyBoolean()))\n        .thenReturn(true);\n\n    request.onLoadFailed(new GlideException(\"test\"));\n\n    verify(builder.target, never()).onLoadFailed(any(Drawable.class));\n  }\n\n  @Test\n  public void testRequestListenerIsCalledWithResourceResult() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    boolean isLoadedFromAlternateCacheKey = true;\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, isLoadedFromAlternateCacheKey);\n\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result), any(Number.class), isAListTarget(), isADataSource(), anyBoolean());\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result),\n            any(Number.class),\n            isAListTarget(),\n            isADataSource(),\n            anyBoolean(),\n            eq(isLoadedFromAlternateCacheKey));\n  }\n\n  @Test\n  public void testRequestListenerIsCalledWithModel() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onResourceReady(\n            any(List.class), eq(builder.model), isAListTarget(), isADataSource(), anyBoolean());\n  }\n\n  @Test\n  public void testRequestListenerIsCalledWithTarget() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onResourceReady(\n            any(List.class), any(Number.class), eq(builder.target), isADataSource(), anyBoolean());\n  }\n\n  @Test\n  public void testRequestListenerIsCalledWithLoadedFromMemoryIfLoadCompletesSynchronously() {\n    final SingleRequest<List> request = builder.addRequestListener(listener1).build();\n\n    when(builder.engine.load(\n            eq(builder.glideContext),\n            eq(builder.model),\n            eq(builder.signature),\n            anyInt(),\n            anyInt(),\n            eq(Object.class),\n            eq(List.class),\n            any(Priority.class),\n            any(DiskCacheStrategy.class),\n            eq(builder.transformations),\n            anyBoolean(),\n            anyBoolean(),\n            any(Options.class),\n            anyBoolean(),\n            anyBoolean(),\n            /* useAnimationPool= */ anyBoolean(),\n            anyBoolean(),\n            any(ResourceCallback.class),\n            anyExecutor()))\n        .thenAnswer(\n            new Answer<Object>() {\n              @Override\n              public Object answer(InvocationOnMock invocation) {\n                request.onResourceReady(\n                    builder.resource,\n                    DataSource.MEMORY_CACHE,\n                    /* isLoadedFromAlternateCacheKey= */ false);\n                return null;\n              }\n            });\n\n    request.begin();\n    request.onSizeReady(100, 100);\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result),\n            any(Number.class),\n            isAListTarget(),\n            eq(DataSource.MEMORY_CACHE),\n            anyBoolean());\n  }\n\n  @Test\n  public void\n      testRequestListenerIsCalledWithNotLoadedFromMemoryCacheIfLoadCompletesAsynchronously() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    request.onSizeReady(100, 100);\n    request.onResourceReady(\n        builder.resource, DataSource.LOCAL, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result),\n            any(Number.class),\n            isAListTarget(),\n            eq(DataSource.LOCAL),\n            anyBoolean());\n  }\n\n  @Test\n  public void testRequestListenerIsCalledWithIsFirstResourceIfNoRequestCoordinator() {\n    SingleRequest<List> request =\n        builder.setRequestCoordinator(null).addRequestListener(listener1).build();\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result), any(Number.class), isAListTarget(), isADataSource(), eq(true));\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result),\n            any(Number.class),\n            isAListTarget(),\n            isADataSource(),\n            eq(true),\n            eq(false));\n  }\n\n  @Test\n  public void testRequestListenerIsCalledWithFirstImageIfRequestCoordinatorReturnsNoResourceSet() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    when(builder.requestCoordinator.isAnyResourceSet()).thenReturn(false);\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result), any(Number.class), isAListTarget(), isADataSource(), eq(true));\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result),\n            any(Number.class),\n            isAListTarget(),\n            isADataSource(),\n            eq(true),\n            eq(false));\n  }\n\n  @Test\n  public void\n      testRequestListenerIsCalledWithNotIsFirstRequestIfRequestCoordinatorReturnsResourceSet() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    when(builder.requestCoordinator.isAnyResourceSet()).thenReturn(true);\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result), any(Number.class), isAListTarget(), isADataSource(), eq(false));\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result),\n            any(Number.class),\n            isAListTarget(),\n            isADataSource(),\n            eq(false),\n            eq(false));\n  }\n\n  @Test\n  public void onResourceReady_notifiesRequestCoordinator_beforeCallingRequestListeners() {\n    AtomicBoolean isRequestCoordinatorVerified = new AtomicBoolean();\n    SingleRequest<List> request =\n        builder\n            .setTarget(new DoNothingTarget())\n            .addRequestListener(\n                new RequestListener<>() {\n                  @Override\n                  public boolean onLoadFailed(\n                      @Nullable GlideException e,\n                      Object model,\n                      @NonNull Target<List> target,\n                      boolean isFirstResource) {\n                    return false;\n                  }\n\n                  @Override\n                  public boolean onResourceReady(\n                      @NonNull List resource,\n                      @NonNull Object model,\n                      Target<List> target,\n                      @NonNull DataSource dataSource,\n                      boolean isFirstResource) {\n                    verify(builder.requestCoordinator).onRequestSuccess(target.getRequest());\n                    isRequestCoordinatorVerified.set(true);\n                    return false;\n                  }\n                })\n            .build();\n    builder.target.setRequest(request);\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    assertThat(isRequestCoordinatorVerified.get()).isTrue();\n  }\n\n  @Test\n  public void onLoadFailed_notifiesRequestCoordinator_beforeCallingRequestListeners() {\n    AtomicBoolean isRequestCoordinatorVerified = new AtomicBoolean();\n    SingleRequest<List> request =\n        builder\n            .setTarget(new DoNothingTarget())\n            .addRequestListener(\n                new RequestListener<>() {\n                  @Override\n                  public boolean onLoadFailed(\n                      @Nullable GlideException e,\n                      Object model,\n                      @NonNull Target<List> target,\n                      boolean isFirstResource) {\n                    verify(builder.requestCoordinator).onRequestFailed(target.getRequest());\n                    isRequestCoordinatorVerified.set(true);\n                    return false;\n                  }\n\n                  @Override\n                  public boolean onResourceReady(\n                      @NonNull List resource,\n                      @NonNull Object model,\n                      Target<List> target,\n                      @NonNull DataSource dataSource,\n                      boolean isFirstResource) {\n                    return false;\n                  }\n                })\n            .build();\n    builder.target.setRequest(request);\n    request.onLoadFailed(new GlideException(\"test\"));\n\n    assertThat(isRequestCoordinatorVerified.get()).isTrue();\n  }\n\n  // We don't need to clear a resource since we're not using it to being with.\n  private static final class DoNothingTarget extends CustomTarget<List> {\n    @Override\n    public void onResourceReady(\n        @NonNull List resource, @Nullable Transition<? super List> transition) {}\n\n    @Override\n    public void onLoadCleared(@Nullable Drawable placeholder) {}\n  }\n\n  @Test\n  public void\n      testRequestListenerIsCalledWithNotIsFirstRequestIfRequestCoordinatorParentReturnsResourceSet() {\n    SingleRequest<List> request = builder.addRequestListener(listener1).build();\n    RequestCoordinator rootRequestCoordinator = mock(RequestCoordinator.class);\n    when(rootRequestCoordinator.isAnyResourceSet()).thenReturn(true);\n    when(builder.requestCoordinator.isAnyResourceSet()).thenReturn(false);\n    when(builder.requestCoordinator.getRoot()).thenReturn(rootRequestCoordinator);\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ true);\n\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result), any(Number.class), isAListTarget(), isADataSource(), eq(false));\n    verify(listener1)\n        .onResourceReady(\n            eq(builder.result),\n            any(Number.class),\n            isAListTarget(),\n            isADataSource(),\n            eq(false),\n            eq(true));\n  }\n\n  @Test\n  public void testTargetIsCalledWithAnimationFromFactory() {\n    SingleRequest<List> request = builder.build();\n    Transition<List> transition = mockTransition();\n    when(builder.transitionFactory.build(any(DataSource.class), anyBoolean()))\n        .thenReturn(transition);\n    request.onResourceReady(\n        builder.resource, DataSource.DATA_DISK_CACHE, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(builder.target).onResourceReady(eq(builder.result), eq(transition));\n  }\n\n  @Test\n  public void testCallsGetSizeIfOverrideWidthIsLessThanZero() {\n    SingleRequest<List> request = builder.setOverrideWidth(-1).setOverrideHeight(100).build();\n    request.begin();\n\n    verify(builder.target).getSize(any(SizeReadyCallback.class));\n  }\n\n  @Test\n  public void testCallsGetSizeIfOverrideHeightIsLessThanZero() {\n    SingleRequest<List> request = builder.setOverrideWidth(100).setOverrideHeight(-1).build();\n    request.begin();\n\n    verify(builder.target).getSize(any(SizeReadyCallback.class));\n  }\n\n  @Test\n  public void testDoesNotCallGetSizeIfOverrideWidthAndHeightAreSet() {\n    SingleRequest<List> request = builder.setOverrideWidth(100).setOverrideHeight(100).build();\n    request.begin();\n\n    verify(builder.target, never()).getSize(any(SizeReadyCallback.class));\n  }\n\n  @Test\n  public void testCallsEngineWithOverrideWidthAndHeightIfSet() {\n    SingleRequest<List> request = builder.setOverrideWidth(1).setOverrideHeight(2).build();\n    request.begin();\n\n    verify(builder.engine)\n        .load(\n            eq(builder.glideContext),\n            eq(builder.model),\n            eq(builder.signature),\n            anyInt(),\n            anyInt(),\n            eq(Object.class),\n            eq(List.class),\n            any(Priority.class),\n            any(DiskCacheStrategy.class),\n            eq(builder.transformations),\n            anyBoolean(),\n            anyBoolean(),\n            any(Options.class),\n            anyBoolean(),\n            anyBoolean(),\n            /* useAnimationPool= */ anyBoolean(),\n            anyBoolean(),\n            any(ResourceCallback.class),\n            anyExecutor());\n  }\n\n  @Test\n  public void testDoesNotSetErrorDrawableIfRequestCoordinatorDoesntAllowIt() {\n    SingleRequest<List> request = builder.setErrorDrawable(new ColorDrawable(Color.RED)).build();\n    when(builder.requestCoordinator.canNotifyStatusChanged(any(Request.class))).thenReturn(false);\n    request.onLoadFailed(new GlideException(\"test\"));\n\n    verify(builder.target, never()).onLoadFailed(any(Drawable.class));\n  }\n\n  @Test\n  public void testCanReRunClearedRequests() {\n    doAnswer(new CallSizeReady(100, 100))\n        .when(builder.target)\n        .getSize(any(SizeReadyCallback.class));\n\n    when(builder.engine.load(\n            eq(builder.glideContext),\n            eq(builder.model),\n            eq(builder.signature),\n            eq(100),\n            eq(100),\n            eq(Object.class),\n            eq(List.class),\n            any(Priority.class),\n            any(DiskCacheStrategy.class),\n            eq(builder.transformations),\n            anyBoolean(),\n            anyBoolean(),\n            any(Options.class),\n            anyBoolean(),\n            anyBoolean(),\n            /* useAnimationPool= */ anyBoolean(),\n            anyBoolean(),\n            any(ResourceCallback.class),\n            anyExecutor()))\n        .thenAnswer(new CallResourceCallback(builder.resource));\n    SingleRequest<List> request = builder.build();\n\n    request.begin();\n    request.clear();\n    request.begin();\n\n    verify(builder.target, times(2)).onResourceReady(eq(builder.result), anyTransition());\n  }\n\n  @Test\n  public void testResourceOnlyReceivesOneGetOnResourceReady() {\n    SingleRequest<List> request = builder.build();\n    request.onResourceReady(\n        builder.resource, DataSource.LOCAL, /* isLoadedFromAlternateCacheKey= */ false);\n\n    verify(builder.resource, times(1)).get();\n  }\n\n  @Test\n  public void testDoesNotStartALoadIfOnSizeReadyIsCalledAfterClear() {\n    SingleRequest<List> request = builder.build();\n    request.clear();\n    request.onSizeReady(100, 100);\n\n    verify(builder.engine, never())\n        .load(\n            eq(builder.glideContext),\n            eq(builder.model),\n            eq(builder.signature),\n            anyInt(),\n            anyInt(),\n            eq(Object.class),\n            eq(List.class),\n            any(Priority.class),\n            any(DiskCacheStrategy.class),\n            eq(builder.transformations),\n            anyBoolean(),\n            anyBoolean(),\n            any(Options.class),\n            anyBoolean(),\n            anyBoolean(),\n            /* useAnimationPool= */ anyBoolean(),\n            anyBoolean(),\n            any(ResourceCallback.class),\n            anyExecutor());\n  }\n\n  @Test\n  public void testCallsSourceUnlimitedExecutorEngineIfOptionsIsSet() {\n    doAnswer(new CallSizeReady(100, 100))\n        .when(builder.target)\n        .getSize(any(SizeReadyCallback.class));\n\n    SingleRequest<List> request = builder.setUseUnlimitedSourceGeneratorsPool(true).build();\n    request.begin();\n\n    verify(builder.engine)\n        .load(\n            eq(builder.glideContext),\n            eq(builder.model),\n            eq(builder.signature),\n            anyInt(),\n            anyInt(),\n            eq(Object.class),\n            eq(List.class),\n            any(Priority.class),\n            any(DiskCacheStrategy.class),\n            eq(builder.transformations),\n            anyBoolean(),\n            anyBoolean(),\n            any(Options.class),\n            anyBoolean(),\n            eq(true),\n            /* useAnimationPool= */ anyBoolean(),\n            anyBoolean(),\n            any(ResourceCallback.class),\n            anyExecutor());\n  }\n\n  @Test\n  public void testCallsSourceExecutorEngineIfOptionsIsSet() {\n    doAnswer(new CallSizeReady(100, 100))\n        .when(builder.target)\n        .getSize(any(SizeReadyCallback.class));\n\n    SingleRequest<List> request = builder.setUseUnlimitedSourceGeneratorsPool(false).build();\n    request.begin();\n\n    verify(builder.engine)\n        .load(\n            eq(builder.glideContext),\n            eq(builder.model),\n            eq(builder.signature),\n            anyInt(),\n            anyInt(),\n            eq(Object.class),\n            eq(List.class),\n            any(Priority.class),\n            any(DiskCacheStrategy.class),\n            eq(builder.transformations),\n            anyBoolean(),\n            anyBoolean(),\n            any(Options.class),\n            anyBoolean(),\n            eq(false),\n            /* useAnimationPool= */ anyBoolean(),\n            anyBoolean(),\n            any(ResourceCallback.class),\n            anyExecutor());\n  }\n\n  @Test\n  // Varargs\n  @SuppressWarnings(\"unchecked\")\n  public void testIsEquivalentTo() {\n    EquivalenceTester<SingleRequestBuilder> tester =\n        EquivalenceTester.of(\n            new Equivalence<SingleRequestBuilder>() {\n              @Override\n              protected boolean doEquivalent(\n                  @NonNull SingleRequestBuilder a, @NonNull SingleRequestBuilder b) {\n                return a.build().isEquivalentTo(b.build()) && b.build().isEquivalentTo(a.build());\n              }\n\n              @Override\n              protected int doHash(@NonNull SingleRequestBuilder listSingleRequest) {\n                return 0;\n              }\n            });\n    tester\n        .addEquivalenceGroup(\n            // Non-null request listeners are treated as equivalent, even if they're not equal.\n            new SingleRequestBuilder().addRequestListener(listener1),\n            new SingleRequestBuilder().addRequestListener(listener2))\n        .addEquivalenceGroup(\n            new SingleRequestBuilder().setOverrideHeight(500),\n            new SingleRequestBuilder().setOverrideHeight(500))\n        .addEquivalenceGroup(\n            new SingleRequestBuilder().setOverrideWidth(500),\n            new SingleRequestBuilder().setOverrideWidth(500))\n        .addEquivalenceGroup(\n            new SingleRequestBuilder().setModel(12345), new SingleRequestBuilder().setModel(12345))\n        .addEquivalenceGroup(\n            new SingleRequestBuilder().setModel(null), new SingleRequestBuilder().setModel(null))\n        .addEquivalenceGroup(\n            new SingleRequestBuilder().setPriority(Priority.LOW),\n            new SingleRequestBuilder().setPriority(Priority.LOW))\n        .test();\n  }\n\n  static final class SingleRequestBuilder {\n    private Engine engine = mock(Engine.class);\n    private Number model = 123456;\n\n    @SuppressWarnings(\"unchecked\")\n    private Target<List> target = mock(Target.class);\n\n    private Resource<List> resource = mockResource();\n    private RequestCoordinator requestCoordinator = mock(RequestCoordinator.class);\n    private Drawable placeholderDrawable = null;\n    private Drawable errorDrawable = null;\n    private Drawable fallbackDrawable = null;\n\n    @SuppressWarnings(\"unchecked\")\n    private List<RequestListener<List>> requestListeners = new ArrayList<>();\n\n    @SuppressWarnings(\"unchecked\")\n    private final TransitionFactory<List> transitionFactory = mock(TransitionFactory.class);\n\n    private int overrideWidth = -1;\n    private int overrideHeight = -1;\n    private List<?> result = new ArrayList<>();\n    private final GlideContext glideContext = mock(GlideContext.class);\n    private final Key signature = new ObjectKey(12345);\n    private Priority priority = Priority.HIGH;\n    private boolean useUnlimitedSourceGeneratorsPool = false;\n    private final Class<List> transcodeClass = List.class;\n    private final Map<Class<?>, Transformation<?>> transformations = new HashMap<>();\n\n    SingleRequestBuilder() {\n      when(glideContext.getExperiments()).thenReturn(mock(GlideExperiments.class));\n      when(requestCoordinator.getRoot()).thenReturn(requestCoordinator);\n      when(requestCoordinator.canSetImage(any(Request.class))).thenReturn(true);\n      when(requestCoordinator.canNotifyCleared(any(Request.class))).thenReturn(true);\n      when(requestCoordinator.canNotifyStatusChanged(any(Request.class))).thenReturn(true);\n      when(resource.get()).thenReturn(result);\n    }\n\n    SingleRequestBuilder setEngine(Engine engine) {\n      this.engine = engine;\n      return this;\n    }\n\n    SingleRequestBuilder setModel(Number model) {\n      this.model = model;\n      return this;\n    }\n\n    SingleRequestBuilder setTarget(Target<List> target) {\n      this.target = target;\n      return this;\n    }\n\n    SingleRequestBuilder setResource(Resource<List> resource) {\n      this.resource = resource;\n      return this;\n    }\n\n    SingleRequestBuilder setRequestCoordinator(RequestCoordinator requestCoordinator) {\n      this.requestCoordinator = requestCoordinator;\n      return this;\n    }\n\n    SingleRequestBuilder setPlaceholderDrawable(Drawable placeholderDrawable) {\n      this.placeholderDrawable = placeholderDrawable;\n      return this;\n    }\n\n    SingleRequestBuilder setErrorDrawable(Drawable errorDrawable) {\n      this.errorDrawable = errorDrawable;\n      return this;\n    }\n\n    SingleRequestBuilder setFallbackDrawable(Drawable fallbackDrawable) {\n      this.fallbackDrawable = fallbackDrawable;\n      return this;\n    }\n\n    SingleRequestBuilder addRequestListener(RequestListener<List> requestListener) {\n      this.requestListeners.add(requestListener);\n      return this;\n    }\n\n    SingleRequestBuilder setOverrideWidth(int overrideWidth) {\n      this.overrideWidth = overrideWidth;\n      return this;\n    }\n\n    SingleRequestBuilder setOverrideHeight(int overrideHeight) {\n      this.overrideHeight = overrideHeight;\n      return this;\n    }\n\n    SingleRequestBuilder setResult(List<?> result) {\n      this.result = result;\n      return this;\n    }\n\n    SingleRequestBuilder setPriority(Priority priority) {\n      this.priority = priority;\n      return this;\n    }\n\n    SingleRequestBuilder setUseUnlimitedSourceGeneratorsPool(\n        boolean useUnlimitedSourceGeneratorsPool) {\n      this.useUnlimitedSourceGeneratorsPool = useUnlimitedSourceGeneratorsPool;\n      return this;\n    }\n\n    SingleRequest<List> build() {\n      RequestOptions requestOptions =\n          new RequestOptions()\n              .error(errorDrawable)\n              .placeholder(placeholderDrawable)\n              .fallback(fallbackDrawable)\n              .override(overrideWidth, overrideHeight)\n              .priority(priority)\n              .signature(signature)\n              .useUnlimitedSourceGeneratorsPool(useUnlimitedSourceGeneratorsPool);\n      return SingleRequest.obtain(\n          /* context= */ glideContext,\n          /* glideContext= */ glideContext,\n          /* requestLock= */ new Object(),\n          model,\n          transcodeClass,\n          requestOptions,\n          overrideWidth,\n          overrideHeight,\n          priority,\n          target,\n          /* targetListener= */ null,\n          requestListeners,\n          requestCoordinator,\n          engine,\n          transitionFactory,\n          Executors.directExecutor());\n    }\n  }\n\n  private static Drawable anyDrawableOrNull() {\n    return any();\n  }\n\n  // TODO do we want to move these to Util?\n  @SuppressWarnings(\"unchecked\")\n  private static <T> Transition<T> mockTransition() {\n    return mock(Transition.class);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static Target<List> isAListTarget() {\n    return isA(Target.class);\n  }\n\n  private static GlideException isAGlideException() {\n    return isA(GlideException.class);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static <T> Transition<T> anyTransition() {\n    return any();\n  }\n\n  private static Executor anyExecutor() {\n    return any(Executor.class);\n  }\n\n  private static class CallResourceCallback implements Answer {\n\n    private final Resource resource;\n\n    CallResourceCallback(Resource resource) {\n      this.resource = resource;\n    }\n\n    @Override\n    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {\n      ResourceCallback cb =\n          (ResourceCallback)\n              invocationOnMock.getArguments()[invocationOnMock.getArguments().length - 2];\n      cb.onResourceReady(resource, DataSource.REMOTE, /* isLoadedFromAlternateCacheKey= */ false);\n      return null;\n    }\n  }\n\n  private static class CallSizeReady implements Answer {\n\n    private final int width;\n    private final int height;\n\n    CallSizeReady(int width, int height) {\n      this.width = width;\n      this.height = height;\n    }\n\n    @Override\n    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {\n      SizeReadyCallback cb = (SizeReadyCallback) invocationOnMock.getArguments()[0];\n      cb.onSizeReady(width, height);\n      return null;\n    }\n  }\n\n  private static class MockTarget implements Target<List> {\n\n    private Drawable currentPlaceholder;\n\n    @Override\n    public void onLoadCleared(@Nullable Drawable placeholder) {\n      currentPlaceholder = placeholder;\n    }\n\n    @Override\n    public void onLoadStarted(@Nullable Drawable placeholder) {\n      currentPlaceholder = placeholder;\n    }\n\n    @Override\n    public void onLoadFailed(@Nullable Drawable errorDrawable) {\n      currentPlaceholder = errorDrawable;\n    }\n\n    @Override\n    public void onResourceReady(\n        @NonNull List resource, @Nullable Transition<? super List> transition) {\n      currentPlaceholder = null;\n    }\n\n    @Override\n    public void getSize(@NonNull SizeReadyCallback cb) {}\n\n    @Override\n    public void removeCallback(@NonNull SizeReadyCallback cb) {\n      // Do nothing.\n    }\n\n    @Override\n    public void setRequest(@Nullable Request request) {}\n\n    @Nullable\n    @Override\n    public Request getRequest() {\n      return null;\n    }\n\n    @Override\n    public void onStart() {}\n\n    @Override\n    public void onStop() {}\n\n    @Override\n    public void onDestroy() {}\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/ThumbnailRequestCoordinatorTest.java",
    "content": "package com.bumptech.glide.request;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\n@RunWith(JUnit4.class)\npublic class ThumbnailRequestCoordinatorTest {\n  @Mock private Request full;\n  @Mock private Request thumb;\n  @Mock private RequestCoordinator parent;\n  private ThumbnailRequestCoordinator coordinator;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    coordinator = newCoordinator();\n    coordinator.setRequests(full, thumb);\n  }\n\n  @Test\n  public void testIsRunningIsFalseIfNeitherRequestIsRunning() {\n    assertFalse(coordinator.isRunning());\n  }\n\n  @Test\n  public void isRunning_withThumbAndFullRunning_isTrue() {\n    coordinator.begin();\n    assertTrue(coordinator.isRunning());\n  }\n\n  @Test\n  public void isRunning_withFullRunning_isTrue() {\n    coordinator.begin();\n    coordinator.onRequestSuccess(thumb);\n    assertTrue(coordinator.isRunning());\n  }\n\n  @Test\n  public void isRunning_withThumbRunning_fullComplete_isFalse() {\n    coordinator.begin();\n    coordinator.onRequestSuccess(full);\n    assertFalse(coordinator.isRunning());\n  }\n\n  @Test\n  public void testStartsFullOnRunIfNotRunning() {\n    coordinator.begin();\n\n    verify(full).begin();\n  }\n\n  @Test\n  public void testStartsThumbOnRunIfNotRunning() {\n    coordinator.begin();\n\n    verify(thumb).begin();\n  }\n\n  @Test\n  public void testDoesNotStartFullOnRunIfRunning() {\n    coordinator.begin();\n    coordinator.begin();\n\n    verify(full, times(1)).begin();\n  }\n\n  @Test\n  public void testDoesNotStartThumbOnRunIfRunning() {\n    coordinator.begin();\n    coordinator.begin();\n\n    verify(thumb, times(1)).begin();\n  }\n\n  @Test\n  public void begin_whenFullIsComplete_startsFull() {\n    coordinator.onRequestSuccess(full);\n\n    coordinator.begin();\n\n    verify(full).begin();\n  }\n\n  @Test\n  public void begin_whenFullIsComplete_doesNotBeginThumb() {\n    coordinator.onRequestSuccess(full);\n\n    coordinator.begin();\n\n    verify(thumb, never()).begin();\n  }\n\n  @Test\n  public void testDoesNotStartFullIfClearedByThumb() {\n    doAnswer(\n            new Answer<Void>() {\n              @Override\n              public Void answer(InvocationOnMock invocation) throws Throwable {\n                coordinator.clear();\n\n                return null;\n              }\n            })\n        .when(thumb)\n        .begin();\n\n    coordinator.begin();\n\n    verify(full, never()).begin();\n  }\n\n  @Test\n  public void testCallsClearOnRequestsWhenCleared() {\n    coordinator.clear();\n    InOrder order = inOrder(thumb, full);\n    order.verify(thumb).clear();\n    order.verify(full).clear();\n  }\n\n  @Test\n  public void pause_pausesThumbAndFullInOrder() {\n    coordinator.begin();\n    coordinator.pause();\n    InOrder order = inOrder(thumb, full);\n    order.verify(thumb).pause();\n    order.verify(full).pause();\n  }\n\n  @Test\n  public void testCanSetImageReturnsTrueForFullRequestIfCoordinatorIsNull() {\n    coordinator = newCoordinator();\n    coordinator.setRequests(full, thumb);\n    assertTrue(coordinator.canSetImage(full));\n  }\n\n  @Test\n  public void testCanSetImageReturnsTrueForFullRequestIfParentAllowsSetImage() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.canSetImage(eq(coordinator))).thenReturn(true);\n    assertTrue(coordinator.canSetImage(full));\n  }\n\n  @Test\n  public void testCanSetImageReturnsFalseForFullRequestIfParentDoesNotAllowSetImage() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.canSetImage(eq(coordinator))).thenReturn(false);\n    assertFalse(coordinator.canSetImage(full));\n  }\n\n  @Test\n  public void canSetImage_forThumb_withNullParent_fullNotComplete_returnsTrue() {\n    assertTrue(coordinator.canSetImage(thumb));\n  }\n\n  @Test\n  public void canSetImage_forThumb_withNullParent_fullComplete_returnsFalse() {\n    coordinator.onRequestSuccess(full);\n    assertFalse(coordinator.canSetImage(thumb));\n  }\n\n  @Test\n  public void canSetImage_forThumb_whenDisallowedByParent_fullNotComplete_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.canSetImage(eq(coordinator))).thenReturn(false);\n    assertFalse(coordinator.canSetImage(thumb));\n  }\n\n  @Test\n  public void canSetImage_forThumb_whenDisallowedByParent_fullComplete_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.canSetImage(eq(coordinator))).thenReturn(false);\n    coordinator.onRequestSuccess(full);\n    assertFalse(coordinator.canSetImage(thumb));\n  }\n\n  @Test\n  public void testCanNotifyStatusChangedIfFullAndNoRequestsAreComplete() {\n    assertTrue(coordinator.canNotifyStatusChanged(full));\n  }\n\n  @Test\n  public void testCanNotNotifyStatusChangedIfThumb() {\n    assertFalse(coordinator.canNotifyStatusChanged(thumb));\n  }\n\n  @Test\n  public void canNotNotifyStatusChanged_forFull_whenFullComplete_isFalse() {\n    when(full.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(full);\n    assertFalse(coordinator.canNotifyStatusChanged(full));\n  }\n\n  @Test\n  public void canNotNotifyStatusChanged_forFull_whenIfThumbComplete_isFalse() {\n    when(thumb.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(thumb);\n    assertFalse(coordinator.canNotifyStatusChanged(full));\n  }\n\n  @Test\n  public void testCanNotNotifyStatusChangedIfParentHasResourceSet() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.isAnyResourceSet()).thenReturn(true);\n    assertFalse(coordinator.canNotifyStatusChanged(full));\n  }\n\n  @Test\n  public void testCanNotifyStatusChangedIfParentAllowsNotify() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.canNotifyStatusChanged(eq(coordinator))).thenReturn(true);\n    assertTrue(coordinator.canNotifyStatusChanged(full));\n  }\n\n  @Test\n  public void testCanNotNotifyStatusChangedIfParentDoesNotAllowNotify() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.canNotifyStatusChanged(eq(coordinator))).thenReturn(false);\n    assertFalse(coordinator.canNotifyStatusChanged(full));\n  }\n\n  @Test\n  public void isAnyResourceSet_withIncompleteThumbAndFull_isFalse() {\n    assertFalse(coordinator.isAnyResourceSet());\n  }\n\n  @Test\n  public void isAnyResourceSet_withCompleteFull_isTrue() {\n    when(full.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(full);\n    assertTrue(coordinator.isAnyResourceSet());\n  }\n\n  @Test\n  public void isAnyResourceSet_withCompleteThumb_isTrue() {\n    when(thumb.isAnyResourceSet()).thenReturn(true);\n    coordinator.onRequestSuccess(thumb);\n    assertTrue(coordinator.isAnyResourceSet());\n  }\n\n  @Test\n  public void isAnyResourceSet_withParentResourceSet_isFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n\n    when(parent.isAnyResourceSet()).thenReturn(true);\n\n    assertThat(coordinator.isAnyResourceSet()).isFalse();\n  }\n\n  @Test\n  public void testIsNotCompleteIfNeitherRequestIsComplete() {\n    assertFalse(coordinator.isComplete());\n  }\n\n  @Test\n  public void isComplete_withFullComplete_isTrue() {\n    coordinator.onRequestSuccess(full);\n    assertTrue(coordinator.isComplete());\n  }\n\n  @Test\n  public void isComplete_withOnlyThumbComplete_returnsFalse() {\n    coordinator.onRequestSuccess(thumb);\n    assertThat(coordinator.isComplete()).isFalse();\n  }\n\n  @Test\n  public void testClearsThumbRequestOnFullRequestComplete_withNullParent() {\n    coordinator.onRequestSuccess(full);\n    verify(thumb).clear();\n  }\n\n  @Test\n  public void testNotifiesParentOnFullRequestComplete_withNonNullParent() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    coordinator.onRequestSuccess(full);\n    verify(parent).onRequestSuccess(eq(coordinator));\n  }\n\n  @Test\n  public void testClearsThumbRequestOnFullRequestComplete_withNonNullParent() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    coordinator.onRequestSuccess(full);\n    verify(thumb).clear();\n  }\n\n  @Test\n  public void testDoesNotClearThumbOnThumbRequestComplete() {\n    coordinator.onRequestSuccess(thumb);\n    verify(thumb, never()).clear();\n  }\n\n  @Test\n  public void testDoesNotClearThumbOnFullComplete_whenThumbIsComplete() {\n    coordinator.onRequestSuccess(thumb);\n    coordinator.onRequestSuccess(full);\n    verify(thumb, never()).clear();\n  }\n\n  @Test\n  public void testDoesNotNotifyParentOnThumbRequestComplete() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    coordinator.onRequestSuccess(thumb);\n\n    verify(parent, never()).onRequestSuccess(any(Request.class));\n  }\n\n  @Test\n  public void canNotifyCleared_withThumbRequest_returnsFalse() {\n    assertThat(coordinator.canNotifyCleared(thumb)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_withFullRequest_andNullParent_returnsTrue() {\n    assertThat(coordinator.canNotifyCleared(full)).isTrue();\n  }\n\n  @Test\n  public void canNotifyCleared_withFullRequest_nonNullParent_parentCanClear_returnsTrue() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.canNotifyCleared(coordinator)).thenReturn(true);\n    assertThat(coordinator.canNotifyCleared(full)).isTrue();\n  }\n\n  @Test\n  public void canNotifyCleared_withFullRequest_nonNullParent_parentCanNotClear_returnsFalse() {\n    coordinator = newCoordinator(parent);\n    coordinator.setRequests(full, thumb);\n    when(parent.canNotifyCleared(coordinator)).thenReturn(false);\n    assertThat(coordinator.canNotifyCleared(full)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_withFullRequest_afterPause_returnsFalse() {\n    coordinator.pause();\n    assertThat(coordinator.canNotifyCleared(full)).isFalse();\n  }\n\n  @Test\n  public void canNotifyCleared_withFullRequest_afterPauseAndResume_returnsTrue() {\n    coordinator.pause();\n    coordinator.begin();\n    assertThat(coordinator.canNotifyCleared(full)).isTrue();\n  }\n\n  @Test\n  public void canNotifyCleared_withFullRequest_afterPauseAndClear_returnsTrue() {\n    coordinator.pause();\n    coordinator.clear();\n    assertThat(coordinator.canNotifyCleared(full)).isTrue();\n  }\n\n  @Test\n  public void testIsEquivalentTo() {\n    ThumbnailRequestCoordinator first = newCoordinator();\n    when(full.isEquivalentTo(full)).thenReturn(true);\n    when(thumb.isEquivalentTo(thumb)).thenReturn(true);\n    first.setRequests(full, thumb);\n    assertTrue(first.isEquivalentTo(first));\n\n    ThumbnailRequestCoordinator second = newCoordinator();\n    second.setRequests(full, full);\n    assertTrue(second.isEquivalentTo(second));\n    assertFalse(second.isEquivalentTo(first));\n    assertFalse(first.isEquivalentTo(second));\n\n    ThumbnailRequestCoordinator third = newCoordinator();\n    third.setRequests(thumb, thumb);\n    assertTrue(third.isEquivalentTo(third));\n    assertFalse(third.isEquivalentTo(first));\n    assertFalse(first.isEquivalentTo(third));\n  }\n\n  private static ThumbnailRequestCoordinator newCoordinator() {\n    return newCoordinator(/* parent= */ null);\n  }\n\n  private static ThumbnailRequestCoordinator newCoordinator(RequestCoordinator parent) {\n    return new ThumbnailRequestCoordinator(/* requestLock= */ new Object(), parent);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/target/AppWidgetTargetTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport android.appwidget.AppWidgetManager;\nimport android.content.ComponentName;\nimport android.graphics.Bitmap;\nimport android.widget.RemoteViews;\nimport androidx.test.core.app.ApplicationProvider;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\nimport org.robolectric.shadow.api.Shadow;\nimport org.robolectric.shadows.ShadowAppWidgetManager;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK, shadows = AppWidgetTargetTest.UpdateShadowAppWidgetManager.class)\npublic class AppWidgetTargetTest {\n  private UpdateShadowAppWidgetManager shadowManager;\n  private RemoteViews views;\n  private int viewId;\n\n  @Before\n  public void setUp() {\n    shadowManager =\n        Shadow.extract(AppWidgetManager.getInstance(ApplicationProvider.getApplicationContext()));\n    viewId = 1234;\n    views = mock(RemoteViews.class);\n  }\n\n  @Test\n  public void testSetsBitmapOnRemoteViewsWithViewIdWhenCreatedWithComponentName() {\n    ComponentName componentName = mock(ComponentName.class);\n    AppWidgetTarget target =\n        new AppWidgetTarget(\n            ApplicationProvider.getApplicationContext(), viewId, views, componentName);\n\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    target.onResourceReady(bitmap, null /*glideAnimation*/);\n\n    verify(views).setImageViewBitmap(eq(viewId), eq(bitmap));\n  }\n\n  @Test\n  public void testUpdatesAppWidgetWhenCreatedWithComponentName() {\n    ComponentName componentName = mock(ComponentName.class);\n    AppWidgetTarget target =\n        new AppWidgetTarget(\n            ApplicationProvider.getApplicationContext(), viewId, views, componentName);\n\n    target.onResourceReady(\n        Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888), null\n        /*glideAnimation*/ );\n\n    assertEquals(componentName, shadowManager.updatedComponentName);\n    assertEquals(views, shadowManager.updatedRemoteViews);\n  }\n\n  @Test\n  public void testSetsBitmapOnRemoteViewsWithViewIdWhenCreatedWithWidgetIds() {\n    int[] widgetIds = new int[] {1};\n    AppWidgetTarget target =\n        new AppWidgetTarget(ApplicationProvider.getApplicationContext(), viewId, views, widgetIds);\n\n    Bitmap bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);\n    target.onResourceReady(bitmap, null /*glideAnimation*/);\n\n    verify(views).setImageViewBitmap(eq(viewId), eq(bitmap));\n  }\n\n  @Test\n  public void testUpdatesAppWidgetWhenCreatedWithWidgetIds() {\n    int[] widgetIds = new int[] {1};\n    AppWidgetTarget target =\n        new AppWidgetTarget(ApplicationProvider.getApplicationContext(), viewId, views, widgetIds);\n\n    target.onResourceReady(\n        Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888), null\n        /*glideAnimation*/ );\n\n    assertThat(widgetIds).isEqualTo(shadowManager.updatedWidgetIds);\n    assertEquals(views, shadowManager.updatedRemoteViews);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsWhenGivenNullContextWithWidgetIds() {\n    new AppWidgetTarget(null /*context*/, 1234 /*viewId*/, views, 1 /*widgetIds*/);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsWhenGivenNullContextWithComponentName() {\n    new AppWidgetTarget(null /*context*/, 1234 /*viewId*/, views, mock(ComponentName.class));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsWhenGivenNullRemoteViewsWithWidgetIds() {\n    new AppWidgetTarget(\n        ApplicationProvider.getApplicationContext(), viewId, null /*remoteViews*/, 1 /*widgetIds*/);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsWhenGivenNullRemoteViewsWithComponentName() {\n    new AppWidgetTarget(\n        ApplicationProvider.getApplicationContext(),\n        viewId,\n        null /*remoteViews*/,\n        mock(ComponentName.class));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsWhenGivenNullWidgetIds() {\n    new AppWidgetTarget(\n        ApplicationProvider.getApplicationContext(), viewId, views, (int[]) null /*widgetIds*/);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsWhenGivenEmptyWidgetIds() {\n    new AppWidgetTarget(ApplicationProvider.getApplicationContext(), viewId, views);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsWhenGivenNullComponentName() {\n    new AppWidgetTarget(\n        ApplicationProvider.getApplicationContext(), viewId, views, (ComponentName) null);\n  }\n\n  @Implements(AppWidgetManager.class)\n  public static class UpdateShadowAppWidgetManager extends ShadowAppWidgetManager {\n    int[] updatedWidgetIds;\n    RemoteViews updatedRemoteViews;\n    ComponentName updatedComponentName;\n\n    @Implementation\n    @Override\n    public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {\n      updatedWidgetIds = appWidgetIds;\n      updatedRemoteViews = views;\n    }\n\n    @Implementation\n    public void updateAppWidget(ComponentName componentName, RemoteViews views) {\n      updatedComponentName = componentName;\n      updatedRemoteViews = views;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/target/BitmapImageViewTargetTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\n\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class BitmapImageViewTargetTest {\n\n  private ImageView view;\n  private BitmapImageViewTarget target;\n\n  @Before\n  public void setUp() {\n    view = new ImageView(ApplicationProvider.getApplicationContext());\n    target = new BitmapImageViewTarget(view);\n  }\n\n  @Test\n  public void testSetsBitmapOnViewInSetResource() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 75, Bitmap.Config.RGB_565);\n    target.setResource(bitmap);\n    assertEquals(bitmap, ((BitmapDrawable) view.getDrawable()).getBitmap());\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/target/ImageViewTargetFactoryTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ImageViewTargetFactoryTest {\n  private ImageViewTargetFactory factory;\n  private ImageView view;\n\n  @Before\n  public void setUp() {\n    factory = new ImageViewTargetFactory();\n    view = new ImageView(ApplicationProvider.getApplicationContext());\n  }\n\n  @Test\n  public void testReturnsTargetForBitmaps() {\n    Bitmap bitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);\n    Target<Bitmap> target = factory.buildTarget(view, Bitmap.class);\n    target.onResourceReady(bitmap, null);\n    assertThat(target).isInstanceOf(BitmapImageViewTarget.class);\n  }\n\n  @Test\n  public void testReturnsTargetForBitmapDrawables() {\n    BitmapDrawable drawable =\n        new BitmapDrawable(\n            ApplicationProvider.getApplicationContext().getResources(),\n            Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_4444));\n\n    Target<BitmapDrawable> target = factory.buildTarget(view, BitmapDrawable.class);\n    target.onResourceReady(drawable, null);\n    assertThat(target).isInstanceOf(DrawableImageViewTarget.class);\n  }\n\n  @Test\n  public void testReturnsTargetForDrawables() {\n    Target<Drawable> target = factory.buildTarget(view, Drawable.class);\n    target.onResourceReady(new ColorDrawable(Color.RED), null);\n    assertThat(target).isInstanceOf(DrawableImageViewTarget.class);\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsForUnknownType() {\n    factory.buildTarget(view, Object.class);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/target/ImageViewTargetTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Color;\nimport android.graphics.drawable.Animatable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.request.transition.Transition;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ImageViewTargetTest {\n\n  @Mock private AnimatedDrawable animatedDrawable;\n  private ImageView view;\n  private TestTarget target;\n  private ColorDrawable drawable;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n\n    view = new ImageView(ApplicationProvider.getApplicationContext());\n    target = new TestTarget(view);\n    drawable = new ColorDrawable(Color.RED);\n  }\n\n  @Test\n  public void testReturnsCurrentDrawable() {\n    view.setImageDrawable(drawable);\n\n    assertEquals(drawable, target.getCurrentDrawable());\n  }\n\n  @Test\n  public void testSetsDrawableSetsDrawableOnView() {\n    target.setDrawable(drawable);\n\n    assertEquals(drawable, view.getDrawable());\n  }\n\n  @Test\n  public void testSetsDrawableOnLoadStarted() {\n    target.onLoadStarted(drawable);\n\n    assertEquals(drawable, view.getDrawable());\n  }\n\n  @Test\n  public void testSetDrawableOnLoadFailed() {\n    target.onLoadFailed(drawable);\n\n    assertEquals(drawable, view.getDrawable());\n  }\n\n  @Test\n  public void testSetsDrawableOnLoadCleared() {\n    target.onLoadCleared(drawable);\n\n    assertEquals(drawable, view.getDrawable());\n  }\n\n  @Test\n  public void testSetsDrawableOnViewInOnResourceReadyWhenAnimationReturnsFalse() {\n    @SuppressWarnings(\"unchecked\")\n    Transition<Drawable> animation = mock(Transition.class);\n    when(animation.transition(any(Drawable.class), eq(target))).thenReturn(false);\n    Drawable resource = new ColorDrawable(Color.GRAY);\n    target.onResourceReady(resource, animation);\n\n    assertEquals(resource, target.resource);\n  }\n\n  @Test\n  public void testDoesNotSetDrawableOnViewInOnResourceReadyWhenAnimationReturnsTrue() {\n    Drawable resource = new ColorDrawable(Color.RED);\n    @SuppressWarnings(\"unchecked\")\n    Transition<Drawable> animation = mock(Transition.class);\n    when(animation.transition(eq(resource), eq(target))).thenReturn(true);\n    target.onResourceReady(resource, animation);\n\n    assertNull(target.resource);\n  }\n\n  @Test\n  public void testProvidesCurrentPlaceholderToAnimationIfPresent() {\n    Drawable placeholder = new ColorDrawable(Color.BLACK);\n    view.setImageDrawable(placeholder);\n\n    @SuppressWarnings(\"unchecked\")\n    Transition<Drawable> animation = mock(Transition.class);\n\n    target.onResourceReady(new ColorDrawable(Color.GREEN), animation);\n\n    ArgumentCaptor<Drawable> drawableCaptor = ArgumentCaptor.forClass(Drawable.class);\n    verify(animation).transition(drawableCaptor.capture(), eq(target));\n    assertThat(((ColorDrawable) drawableCaptor.getValue()).getColor()).isEqualTo(Color.GREEN);\n  }\n\n  @Test\n  public void onResourceReady_withAnimatableResource_startsAnimatableAfterSetResource() {\n    AnimatedDrawable drawable = mock(AnimatedDrawable.class);\n    ImageView view = mock(ImageView.class);\n    target = new TestTarget(view);\n    target.onResourceReady(drawable, /* transition= */ null);\n\n    InOrder order = inOrder(view, drawable);\n    order.verify(view).setImageDrawable(drawable);\n    order.verify(drawable).start();\n  }\n\n  @Test\n  public void onLoadCleared_withAnimatableDrawable_stopsDrawable() {\n    target.onResourceReady(animatedDrawable, /* transition= */ null);\n    verify(animatedDrawable).start();\n    verify(animatedDrawable, never()).stop();\n\n    target.onLoadCleared(/* placeholder= */ null);\n\n    verify(animatedDrawable).stop();\n  }\n\n  private abstract static class AnimatedDrawable extends Drawable implements Animatable {\n    // Intentionally empty.\n  }\n\n  private static final class TestTarget extends ImageViewTarget<Drawable> {\n    public Drawable resource;\n\n    TestTarget(ImageView view) {\n      super(view);\n    }\n\n    @Override\n    protected void setResource(Drawable resource) {\n      this.resource = resource;\n      view.setImageDrawable(resource);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/target/NotificationTargetTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport android.app.Notification;\nimport android.app.NotificationManager;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.widget.RemoteViews;\nimport androidx.test.core.app.ApplicationProvider;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\nimport org.robolectric.shadow.api.Shadow;\nimport org.robolectric.shadows.ShadowNotificationManager;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(\n    sdk = ROBOLECTRIC_SDK,\n    shadows = NotificationTargetTest.UpdateShadowNotificationManager.class)\npublic class NotificationTargetTest {\n  private UpdateShadowNotificationManager shadowManager;\n  private RemoteViews remoteViews;\n  private int viewId;\n  private Notification notification;\n  private int notificationId;\n  private String notificationTag;\n  private NotificationTarget target;\n\n  @Before\n  public void setUp() {\n    NotificationManager notificationManager =\n        (NotificationManager)\n            ApplicationProvider.getApplicationContext()\n                .getSystemService(Context.NOTIFICATION_SERVICE);\n    shadowManager = Shadow.extract(notificationManager);\n\n    remoteViews = mock(RemoteViews.class);\n    viewId = 123;\n    notification = mock(Notification.class);\n    notificationId = 456;\n    notificationTag = \"tag\";\n\n    target =\n        new NotificationTarget(\n            ApplicationProvider.getApplicationContext(),\n            100 /*width*/,\n            100 /*height*/,\n            viewId,\n            remoteViews,\n            notification,\n            notificationId,\n            notificationTag);\n  }\n\n  @Test\n  public void testSetsBitmapOnRemoteViewsWithGivenImageIdOnResourceReady() {\n    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);\n    target.onResourceReady(bitmap, null /*glideAnimation*/);\n    verify(remoteViews).setImageViewBitmap(eq(viewId), eq(bitmap));\n  }\n\n  @Test\n  public void updatesNotificationManagerWithNotificationIdAndNotificationOnResourceReady() {\n    target.onResourceReady(\n        Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888), null\n        /*glideAnimation*/ );\n\n    assertEquals(notificationId, shadowManager.updatedNotificationId);\n    assertEquals(notificationTag, shadowManager.updatedNotificationTag);\n    assertEquals(notification, shadowManager.updatedNotification);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfContextIsNull() {\n    new NotificationTarget(\n        null /*context*/,\n        100 /*width*/,\n        100 /*height*/,\n        123 /*viewId*/,\n        mock(RemoteViews.class),\n        mock(Notification.class),\n        456 /*notificationId*/,\n        \"tag\" /*notificationTag*/);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfNotificationIsNull() {\n    new NotificationTarget(\n        ApplicationProvider.getApplicationContext(),\n        100 /*width*/,\n        100 /*height*/,\n        123 /*viewId*/,\n        mock(RemoteViews.class),\n        null /*notification*/,\n        456 /*notificationId*/,\n        \"tag\" /*notificationTag*/);\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfRemoteViewsIsNull() {\n    new NotificationTarget(\n        ApplicationProvider.getApplicationContext(),\n        100 /*width*/,\n        100 /*height*/,\n        123 /*viewId*/,\n        null /*remoteViews*/,\n        mock(Notification.class),\n        456 /*notificationId*/,\n        \"tag\" /*notificationTag*/);\n  }\n\n  @Implements(NotificationManager.class)\n  public static class UpdateShadowNotificationManager extends ShadowNotificationManager {\n    int updatedNotificationId;\n    String updatedNotificationTag;\n    Notification updatedNotification;\n\n    @Implementation\n    @Override\n    public void notify(String notificationTag, int notificationId, Notification notification) {\n      updatedNotificationTag = notificationTag;\n      updatedNotificationId = notificationId;\n      updatedNotification = notification;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/target/PreloadTargetTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.robolectric.Shadows.shadowOf;\n\nimport android.os.Looper;\nimport com.bumptech.glide.RequestManager;\nimport com.bumptech.glide.request.Request;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class PreloadTargetTest {\n\n  @Mock private RequestManager requestManager;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n    shadowOf(Looper.getMainLooper()).pause();\n  }\n\n  @Test\n  public void testCallsSizeReadyWithGivenDimensions() {\n    int width = 1234;\n    int height = 456;\n    PreloadTarget<Object> target = PreloadTarget.obtain(requestManager, width, height);\n    SizeReadyCallback cb = mock(SizeReadyCallback.class);\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  // This isn't really supposed to happen, but just to double check...\n  @Test\n  public void onResourceReady_withNullRequest_doesNotClearTarget() {\n    PreloadTarget<Object> target = PreloadTarget.obtain(requestManager, 100, 100);\n    target.setRequest(null);\n\n    callOnResourceReadyAndRunUiRunnables(target);\n\n    verify(requestManager, never()).clear(target);\n  }\n\n  @Test\n  public void onResourceReady_withNotYetCompleteRequest_doesNotClearTarget() {\n    Request request = mock(Request.class);\n    when(request.isComplete()).thenReturn(false);\n\n    PreloadTarget<Object> target = PreloadTarget.obtain(requestManager, 100, 100);\n    target.setRequest(request);\n\n    callOnResourceReadyAndRunUiRunnables(target);\n\n    verify(requestManager, never()).clear(target);\n  }\n\n  @Test\n  public void onResourceReady_withCompleteRequest_postsToClearTarget() {\n    Request request = mock(Request.class);\n    when(request.isComplete()).thenReturn(true);\n\n    PreloadTarget<Object> target = PreloadTarget.obtain(requestManager, 100, 100);\n    target.setRequest(request);\n\n    callOnResourceReadyAndRunUiRunnables(target);\n\n    verify(requestManager).clear(target);\n  }\n\n  @Test\n  public void onResourceReady_withCompleteRequest_doesNotImmediatelyClearTarget() {\n    Request request = mock(Request.class);\n    when(request.isComplete()).thenReturn(true);\n\n    PreloadTarget<Object> target = PreloadTarget.obtain(requestManager, 100, 100);\n    target.setRequest(request);\n\n    target.onResourceReady(new Object(), /* transition= */ null);\n\n    verify(requestManager, never()).clear(target);\n  }\n\n  private void callOnResourceReadyAndRunUiRunnables(Target<Object> target) {\n    target.onResourceReady(new Object(), /* transition= */ null);\n    shadowOf(Looper.getMainLooper()).idle();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/target/SimpleTargetTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static org.mockito.Mockito.mock;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.request.transition.Transition;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class SimpleTargetTest {\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsOnGetSizeIfGivenWidthIsLessThanZero() {\n    getTarget(-1, 1).getSize(mock(SizeReadyCallback.class));\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsOnGetSizeIfGivenWidthIsEqualToZero() {\n    getTarget(0, 1).getSize(mock(SizeReadyCallback.class));\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsOnGetSizeIfGivenHeightIsLessThanZero() {\n    getTarget(1, -1).getSize(mock(SizeReadyCallback.class));\n  }\n\n  @Test(expected = IllegalArgumentException.class)\n  public void testThrowsOnGetSizeIfGivenHeightIsEqualToZero() {\n    getTarget(1, 0).getSize(mock(SizeReadyCallback.class));\n  }\n\n  @Test\n  public void testCanBeConstructedWithoutDimensions() {\n    new SimpleTarget<Object>() {\n      @Override\n      public void onResourceReady(\n          @NonNull Object resource, @Nullable Transition<? super Object> transition) {\n        // Do nothing.\n      }\n    };\n  }\n\n  @Test\n  public void testConstructorDoesNotThrowWithSizeOriginal() {\n    getTarget(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);\n  }\n\n  @Test\n  public void testGetSizeDoesNotThrowWithSizeOriginal() {\n    getTarget(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).getSize(mock(SizeReadyCallback.class));\n  }\n\n  private SimpleTarget<Object> getTarget(int width, int height) {\n    return new SimpleTarget<Object>(width, height) {\n      @Override\n      public void onResourceReady(\n          @NonNull Object resource, @Nullable Transition<? super Object> transition) {\n        // Do nothing.\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/target/ViewTargetTest.java",
    "content": "package com.bumptech.glide.request.target;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.view.Display;\nimport android.view.View;\nimport android.view.View.OnAttachStateChangeListener;\nimport android.view.ViewGroup.LayoutParams;\nimport android.view.ViewTreeObserver;\nimport android.view.ViewTreeObserver.OnPreDrawListener;\nimport android.view.WindowManager;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.tests.Util;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\nimport org.robolectric.annotation.Config;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\nimport org.robolectric.annotation.RealObject;\nimport org.robolectric.shadow.api.Shadow;\nimport org.robolectric.shadows.ShadowView;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(\n    sdk = ROBOLECTRIC_SDK,\n    shadows = {\n      ViewTargetTest.SizedShadowView.class,\n      ViewTargetTest.PreDrawShadowViewTreeObserver.class\n    })\npublic class ViewTargetTest {\n  private View view;\n  private ViewTarget<View, Object> target;\n  private SizedShadowView shadowView;\n  private PreDrawShadowViewTreeObserver shadowObserver;\n  @Mock private SizeReadyCallback cb;\n  @Mock private Request request;\n  private int sdkVersion;\n  private AttachStateTarget attachStateTarget;\n\n  @Before\n  public void setUp() {\n    sdkVersion = Build.VERSION.SDK_INT;\n    MockitoAnnotations.initMocks(this);\n    view = new View(ApplicationProvider.getApplicationContext());\n    target = new TestViewTarget(view);\n    attachStateTarget = new AttachStateTarget(view);\n\n    shadowView = Shadow.extract(view);\n    shadowObserver = Shadow.extract(view.getViewTreeObserver());\n  }\n\n  @After\n  public void tearDown() {\n    Util.setSdkVersionInt(sdkVersion);\n    ViewTarget.SizeDeterminer.maxDisplayLength = null;\n  }\n\n  @Test\n  public void testReturnsWrappedView() {\n    assertEquals(view, target.getView());\n  }\n\n  @Test\n  public void testReturnsNullFromGetRequestIfNoRequestSet() {\n    assertNull(target.getRequest());\n  }\n\n  @Test\n  public void testCanSetAndRetrieveRequest() {\n    target.setRequest(request);\n\n    assertEquals(request, target.getRequest());\n  }\n\n  @Test\n  public void testRetrievesRequestFromPreviousTargetForView() {\n    target.setRequest(request);\n\n    ViewTarget<View, Object> second = new TestViewTarget(view);\n\n    assertEquals(request, second.getRequest());\n  }\n\n  @Test\n  public void testSizeCallbackIsCalledSynchronouslyIfViewSizeSet() {\n    int dimens = 333;\n    shadowView.setWidth(dimens).setHeight(dimens).setIsLaidOut(true);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(eq(dimens), eq(dimens));\n  }\n\n  @Test\n  public void testSizeCallbackIsCalledSynchronouslyIfLayoutParamsConcreteSizeSet() {\n    int dimens = 444;\n    LayoutParams layoutParams = new LayoutParams(dimens, dimens);\n    view.setLayoutParams(layoutParams);\n    shadowView.setIsLaidOut(true);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(eq(dimens), eq(dimens));\n  }\n\n  @Test\n  public void getSize_withBothWrapContent_usesDisplayDimens() {\n    LayoutParams layoutParams =\n        new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);\n    view.setLayoutParams(layoutParams);\n    shadowView.setIsLaidOut(true);\n\n    setDisplayDimens(200, 300);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(300, 300);\n  }\n\n  @Test\n  public void getSize_withWrapContentWidthAndValidHeight_usesDisplayDimenAndValidHeight() {\n    int height = 100;\n    LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, height);\n    view.setLayoutParams(params);\n    shadowView.setIsLaidOut(true);\n\n    setDisplayDimens(100, 200);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(200, height);\n  }\n\n  @Test\n  public void getSize_withWrapContentHeightAndValidWidth_returnsWidthAndDisplayDimen() {\n    int width = 100;\n    LayoutParams params = new LayoutParams(width, LayoutParams.WRAP_CONTENT);\n    view.setLayoutParams(params);\n    shadowView.setIsLaidOut(true);\n\n    setDisplayDimens(200, 100);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(width, 200);\n  }\n\n  @Test\n  public void getSize_withWrapContentWidthAndMatchParentHeight_usesDisplayDimenWidthAndHeight() {\n    LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);\n    view.setLayoutParams(params);\n\n    setDisplayDimens(500, 600);\n\n    target.getSize(cb);\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n\n    int height = 32;\n    shadowView.setHeight(height).setIsLaidOut(true);\n\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb).onSizeReady(600, height);\n  }\n\n  @Test\n  public void getSize_withMatchParentWidthAndWrapContentHeight_usesWidthAndDisplayDimenHeight() {\n    LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);\n    view.setLayoutParams(params);\n\n    setDisplayDimens(300, 400);\n\n    target.getSize(cb);\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n\n    int width = 32;\n    shadowView.setWidth(width).setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb).onSizeReady(width, 400);\n  }\n\n  @Test\n  public void testMatchParentWidthAndHeight() {\n    LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);\n    view.setLayoutParams(params);\n\n    target.getSize(cb);\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n\n    int width = 32;\n    int height = 45;\n    shadowView.setWidth(width).setHeight(height).setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test\n  public void testSizeCallbackIsCalledPreDrawIfNoDimensAndNoLayoutParams() {\n    target.getSize(cb);\n\n    int width = 12;\n    int height = 32;\n    shadowView.setWidth(width).setHeight(height).setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test\n  public void testSizeCallbacksAreCalledInOrderPreDraw() {\n    SizeReadyCallback[] cbs = new SizeReadyCallback[25];\n    for (int i = 0; i < cbs.length; i++) {\n      cbs[i] = mock(SizeReadyCallback.class);\n      target.getSize(cbs[i]);\n    }\n\n    int width = 100;\n    int height = 111;\n    shadowView.setWidth(width).setHeight(height).setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n\n    InOrder order = inOrder((Object[]) cbs);\n    for (SizeReadyCallback cb : cbs) {\n      order.verify(cb).onSizeReady(eq(width), eq(height));\n    }\n  }\n\n  @Test\n  public void testDoesNotNotifyCallbackTwiceIfAddedTwice() {\n    target.getSize(cb);\n    target.getSize(cb);\n\n    view.setLayoutParams(new LayoutParams(100, 100));\n    shadowView.setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb, times(1)).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void testDoesNotAddMultipleListenersIfMultipleCallbacksAreAdded() {\n    SizeReadyCallback cb1 = mock(SizeReadyCallback.class);\n    SizeReadyCallback cb2 = mock(SizeReadyCallback.class);\n    target.getSize(cb1);\n    target.getSize(cb2);\n    assertThat(shadowObserver.getPreDrawListeners()).hasSize(1);\n  }\n\n  @Test\n  public void testDoesAddSecondListenerIfFirstListenerIsRemovedBeforeSecondRequest() {\n    SizeReadyCallback cb1 = mock(SizeReadyCallback.class);\n    target.getSize(cb1);\n\n    view.setLayoutParams(new LayoutParams(100, 100));\n    shadowView.setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n\n    assertThat(shadowObserver.getPreDrawListeners()).hasSize(0);\n\n    SizeReadyCallback cb2 = mock(SizeReadyCallback.class);\n    view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));\n    target.getSize(cb2);\n\n    view.setLayoutParams(new LayoutParams(100, 100));\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb2).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void testSizeCallbackIsNotCalledPreDrawIfNoDimensSetOnPreDraw() {\n    target.getSize(cb);\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n    assertThat(shadowObserver.getPreDrawListeners()).hasSize(1);\n  }\n\n  @Test\n  public void testSizeCallbackIsCalledPreDrawIfNoDimensAndNoLayoutParamsButLayoutParamsSetLater() {\n    target.getSize(cb);\n\n    int width = 689;\n    int height = 354;\n    LayoutParams layoutParams = new LayoutParams(width, height);\n    view.setLayoutParams(layoutParams);\n    shadowView.setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test\n  public void testCallbackIsNotCalledTwiceIfPreDrawFiresTwice() {\n    target.getSize(cb);\n\n    LayoutParams layoutParams = new LayoutParams(1234, 4123);\n    view.setLayoutParams(layoutParams);\n    shadowView.setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb, times(1)).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void testCallbacksFromMultipleRequestsAreNotifiedOnPreDraw() {\n    SizeReadyCallback firstCb = mock(SizeReadyCallback.class);\n    SizeReadyCallback secondCb = mock(SizeReadyCallback.class);\n    target.getSize(firstCb);\n    target.getSize(secondCb);\n\n    int width = 68;\n    int height = 875;\n    LayoutParams layoutParams = new LayoutParams(width, height);\n    view.setLayoutParams(layoutParams);\n    shadowView.setIsLaidOut(true);\n    shadowObserver.fireOnPreDrawListeners();\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(firstCb, times(1)).onSizeReady(eq(width), eq(height));\n    verify(secondCb, times(1)).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test\n  public void testDoesNotThrowOnPreDrawIfViewTreeObserverIsDead() {\n    target.getSize(cb);\n\n    int width = 1;\n    int height = 2;\n    LayoutParams layoutParams = new LayoutParams(width, height);\n    view.setLayoutParams(layoutParams);\n    shadowView.setIsLaidOut(true);\n    shadowObserver.setIsAlive(false);\n    shadowObserver.fireOnPreDrawListeners();\n\n    verify(cb).onSizeReady(eq(width), eq(height));\n  }\n\n  @Test(expected = NullPointerException.class)\n  public void testThrowsIfGivenNullView() {\n    new TestViewTarget(null);\n  }\n\n  @Test\n  public void testDecreasesDimensionsByViewPadding() {\n    view.setLayoutParams(new LayoutParams(100, 100));\n    view.setPadding(25, 25, 25, 25);\n    shadowView.setIsLaidOut(true);\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(50, 50);\n  }\n\n  @Test\n  public void getSize_withValidWidthAndHeight_notLaidOut_notLayoutRequested_callsSizeReady() {\n    shadowView.setWidth(100).setHeight(100).setIsLaidOut(false);\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(100, 100);\n  }\n\n  @Test\n  public void getSize_withLayoutParams_notLaidOut_doesCallSizeReady() {\n    shadowView\n        .setLayoutParams(new LayoutParams(10, 10))\n        .setWidth(100)\n        .setHeight(100)\n        .setIsLaidOut(false);\n    target.getSize(cb);\n\n    verify(cb, times(1)).onSizeReady(anyInt(), anyInt());\n  }\n\n  @Test\n  public void getSize_withLayoutParams_emptyParams_notLaidOutOrLayoutRequested_callsSizeReady() {\n    shadowView\n        .setLayoutParams(new LayoutParams(0, 0))\n        .setWidth(100)\n        .setHeight(100)\n        .setIsLaidOut(false);\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(100, 100);\n  }\n\n  @Test\n  public void getSize_withValidWidthAndHeight_preV19_layoutRequested_callsSizeReady() {\n    Util.setSdkVersionInt(18);\n    shadowView.setWidth(100).setHeight(100).requestLayout();\n\n    target.getSize(cb);\n\n    verify(cb).onSizeReady(100, 100);\n  }\n\n  @Test\n  public void getSize_withWidthAndHeightEqualToPadding_doesNotCallSizeReady() {\n    shadowView.setWidth(100).setHeight(100).setIsLaidOut(true);\n\n    view.setPadding(50, 50, 50, 50);\n\n    target.getSize(cb);\n\n    verify(cb, never()).onSizeReady(anyInt(), anyInt());\n  }\n\n  private void setDisplayDimens(Integer width, Integer height) {\n    WindowManager windowManager =\n        (WindowManager)\n            ApplicationProvider.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);\n    Display display = Preconditions.checkNotNull(windowManager).getDefaultDisplay();\n    if (width != null) {\n      Shadows.shadowOf(display).setWidth(width);\n    }\n\n    if (height != null) {\n      Shadows.shadowOf(display).setHeight(height);\n    }\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_withNullRequest_doesNothing() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(null);\n    shadowView.callOnAttachedToWindow();\n  }\n\n  // This behavior isn't clearly correct, but it doesn't seem like there's any harm to clear an\n  // already cleared request, so we might as well avoid the extra check/complexity in the code.\n  @Test\n  public void clearOnDetach_onDetach_withClearedRequest_clearsRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    shadowView.callOnDetachedFromWindow();\n\n    verify(request).clear();\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_withRunningRequest_pausesRequestOnce() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    shadowView.callOnDetachedFromWindow();\n\n    verify(request).clear();\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_afterOnLoadCleared_removesListener() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n    attachStateTarget.setRequest(request);\n    shadowView.callOnDetachedFromWindow();\n\n    verify(request, never()).clear();\n  }\n\n  @Test\n  public void clearOnDetach_moreThanOnce_registersObserverOnce() {\n    attachStateTarget.clearOnDetach().clearOnDetach();\n\n    assertThat(shadowView.attachStateListeners).hasSize(1);\n  }\n\n  @Test\n  public void clearOnDetach_onDetach_afterMultipleClearOnDetaches_removesListener() {\n    attachStateTarget.clearOnDetach().clearOnDetach().clearOnDetach();\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n    attachStateTarget.setRequest(request);\n    shadowView.callOnDetachedFromWindow();\n\n    verify(request, never()).clear();\n  }\n\n  // This behavior isn't clearly correct, but it doesn't seem like there's any harm to clear an\n  // already cleared request, so we might as well avoid the extra check/complexity in the code.\n  @Test\n  public void clearOnDetach_onDetach_afterLoadCleared_clearsRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    shadowView.callOnDetachedFromWindow();\n\n    verify(request).clear();\n  }\n\n  @Test\n  public void clearOnDetach_onAttach_withNullRequest_doesNothing() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(null);\n    shadowView.callOnAttachedToWindow();\n  }\n\n  @Test\n  public void clearOnDetach_onAttach_withRunningRequest_doesNotBeginRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(false);\n    shadowView.callOnAttachedToWindow();\n\n    verify(request, never()).begin();\n  }\n\n  @Test\n  public void clearOnDetach_onAttach_withClearedRequest_beginsRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    shadowView.callOnAttachedToWindow();\n\n    verify(request).begin();\n  }\n\n  @Test\n  public void clearOnDetach_afterLoadClearedAndRestarted_onAttach_beingsRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n    attachStateTarget.onLoadStarted(/* placeholder= */ null);\n    shadowView.callOnAttachedToWindow();\n\n    verify(request).begin();\n  }\n\n  @Test\n  public void clearOnDetach_onAttach_afterLoadCleared_doesNotBeingRequest() {\n    attachStateTarget.clearOnDetach();\n    attachStateTarget.setRequest(request);\n    when(request.isCleared()).thenReturn(true);\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n    shadowView.callOnAttachedToWindow();\n\n    verify(request, never()).begin();\n  }\n\n  @Test\n  public void onLoadStarted_withoutClearOnDetach_doesNotAddListener() {\n    attachStateTarget.onLoadStarted(/* placeholder= */ null);\n\n    assertThat(shadowView.attachStateListeners).isEmpty();\n  }\n\n  // containsExactly does not need its result checked.\n  @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n  @Test\n  public void onLoadCleared_withoutClearOnDetach_doesNotRemoveListeners() {\n    OnAttachStateChangeListener expected =\n        new OnAttachStateChangeListener() {\n          @Override\n          public void onViewAttachedToWindow(View v) {}\n\n          @Override\n          public void onViewDetachedFromWindow(View v) {}\n        };\n    shadowView.addOnAttachStateChangeListener(expected);\n\n    attachStateTarget.onLoadCleared(/* placeholder= */ null);\n\n    assertThat(shadowView.attachStateListeners).containsExactly(expected);\n  }\n\n  @Implements(ViewTreeObserver.class)\n  public static final class PreDrawShadowViewTreeObserver {\n    private final CopyOnWriteArrayList<OnPreDrawListener> preDrawListeners =\n        new CopyOnWriteArrayList<>();\n    private boolean isAlive = true;\n\n    @SuppressWarnings(\"unused\")\n    @Implementation\n    public void addOnPreDrawListener(OnPreDrawListener listener) {\n      checkIsAlive();\n      preDrawListeners.add(listener);\n    }\n\n    @SuppressWarnings(\"unused\")\n    @Implementation\n    public void removeOnPreDrawListener(OnPreDrawListener listener) {\n      checkIsAlive();\n      preDrawListeners.remove(listener);\n    }\n\n    @Implementation\n    @SuppressWarnings(\"WeakerAccess\")\n    public boolean isAlive() {\n      return isAlive;\n    }\n\n    private void checkIsAlive() {\n      if (!isAlive()) {\n        throw new IllegalStateException(\"ViewTreeObserver is not alive!\");\n      }\n    }\n\n    void setIsAlive(@SuppressWarnings(\"SameParameterValue\") boolean isAlive) {\n      this.isAlive = isAlive;\n    }\n\n    void fireOnPreDrawListeners() {\n      for (OnPreDrawListener listener : preDrawListeners) {\n        listener.onPreDraw();\n      }\n    }\n\n    List<OnPreDrawListener> getPreDrawListeners() {\n      return preDrawListeners;\n    }\n  }\n\n  // Shadows require stronger access and unused values.\n  @SuppressWarnings({\"UnusedReturnValue\", \"WeakerAccess\", \"unused\"})\n  @Implements(View.class)\n  public static final class SizedShadowView extends ShadowView {\n    @RealObject private View view;\n    private int width;\n    private int height;\n    private LayoutParams layoutParams;\n    private boolean isLaidOut;\n    private boolean isLayoutRequested;\n    final Set<OnAttachStateChangeListener> attachStateListeners = new HashSet<>();\n\n    public SizedShadowView setWidth(int width) {\n      this.width = width;\n      return this;\n    }\n\n    public SizedShadowView setHeight(int height) {\n      this.height = height;\n      return this;\n    }\n\n    @Implementation\n    public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {\n      attachStateListeners.add(listener);\n    }\n\n    @Implementation\n    public void removeOnAttachStateChangeListener(OnAttachStateChangeListener listener) {\n      attachStateListeners.remove(listener);\n    }\n\n    @Implementation\n    public void onAttachedToWindow() {\n      for (OnAttachStateChangeListener listener : attachStateListeners) {\n        listener.onViewAttachedToWindow(view);\n      }\n    }\n\n    @Implementation\n    public void onDetachedFromWindow() {\n      for (OnAttachStateChangeListener listener : attachStateListeners) {\n        listener.onViewDetachedFromWindow(view);\n      }\n    }\n\n    @Override\n    public void callOnAttachedToWindow() {\n      super.callOnAttachedToWindow();\n    }\n\n    @Override\n    public void callOnDetachedFromWindow() {\n      super.callOnDetachedFromWindow();\n    }\n\n    @Implementation\n    public SizedShadowView setLayoutParams(LayoutParams layoutParams) {\n      this.layoutParams = layoutParams;\n      return this;\n    }\n\n    @Implementation\n    public SizedShadowView setIsLaidOut(boolean isLaidOut) {\n      this.isLaidOut = isLaidOut;\n      return this;\n    }\n\n    @Implementation\n    @Override\n    public void requestLayout() {\n      isLayoutRequested = true;\n    }\n\n    @Implementation\n    public int getWidth() {\n      return width;\n    }\n\n    @Implementation\n    public int getHeight() {\n      return height;\n    }\n\n    @Implementation\n    public boolean isLaidOut() {\n      return isLaidOut;\n    }\n\n    @Implementation\n    public boolean isLayoutRequested() {\n      return isLayoutRequested;\n    }\n\n    @Implementation\n    public LayoutParams getLayoutParams() {\n      return layoutParams;\n    }\n  }\n\n  private static final class AttachStateTarget extends ViewTarget<View, Object> {\n    AttachStateTarget(View view) {\n      super(view);\n    }\n\n    @Override\n    public void onResourceReady(\n        @NonNull Object resource, @Nullable Transition<? super Object> transition) {}\n  }\n\n  private static final class TestViewTarget extends ViewTarget<View, Object> {\n\n    TestViewTarget(View view) {\n      super(view);\n    }\n\n    // We're intentionally avoiding the super call.\n    @SuppressWarnings(\"MissingSuperCall\")\n    @Override\n    public void onResourceReady(\n        @NonNull Object resource, @Nullable Transition<? super Object> transition) {\n      // Avoid calling super.\n    }\n\n    // We're intentionally avoiding the super call.\n    @SuppressWarnings(\"MissingSuperCall\")\n    @Override\n    public void onLoadCleared(@Nullable Drawable placeholder) {\n      // Avoid calling super.\n    }\n\n    // We're intentionally avoiding the super call.\n    @SuppressWarnings(\"MissingSuperCall\")\n    @Override\n    public void onLoadStarted(@Nullable Drawable placeholder) {\n      // Avoid calling super.\n    }\n\n    // We're intentionally avoiding the super call.\n    @SuppressWarnings(\"MissingSuperCall\")\n    @Override\n    public void onLoadFailed(@Nullable Drawable errorDrawable) {\n      // Avoid calling super.\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/transition/DrawableCrossFadeFactoryTest.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotEquals;\n\nimport android.graphics.drawable.Drawable;\nimport com.bumptech.glide.load.DataSource;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class DrawableCrossFadeFactoryTest {\n\n  private DrawableCrossFadeFactory factory;\n\n  @SuppressWarnings(\"unchecked\")\n  @Before\n  public void setUp() {\n    factory = new DrawableCrossFadeFactory(100 /*duration*/, false /*isCrossFadeEnabled*/);\n  }\n\n  @Test\n  public void testReturnsNoAnimationIfFromMemoryCache() {\n    assertEquals(\n        NoTransition.<Drawable>get(),\n        factory.build(DataSource.MEMORY_CACHE, true /*isFirstResource*/));\n  }\n\n  @Test\n  public void testReturnsReturnsAnimationIfNotFromMemoryCacheAndIsFirstResource() {\n    assertNotEquals(\n        NoTransition.<Drawable>get(),\n        factory.build(DataSource.DATA_DISK_CACHE, true /*isFirstResource*/));\n  }\n\n  @Test\n  public void testReturnsAnimationIfNotFromMemoryCacheAndNotIsFirstResource() {\n    assertNotEquals(\n        NoTransition.<Drawable>get(),\n        factory.build(DataSource.DATA_DISK_CACHE, false /*isFirstResource*/));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/transition/DrawableCrossFadeViewAnimationTest.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.TransitionDrawable;\nimport com.bumptech.glide.request.transition.Transition.ViewAdapter;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class DrawableCrossFadeViewAnimationTest {\n  private CrossFadeHarness harness;\n\n  @Before\n  public void setup() {\n    harness = new CrossFadeHarness();\n  }\n\n  @Test\n  public void testIgnoresNullViews() {\n    when(harness.adapter.getView()).thenReturn(null);\n    harness.animation.transition(harness.current, harness.adapter);\n  }\n\n  @Test\n  public void transition_withNonNullPreviousDrawable_setsTransitionDrawable() {\n    Drawable previous = new ColorDrawable(Color.WHITE);\n    when(harness.adapter.getCurrentDrawable()).thenReturn(previous);\n    harness.animation.transition(harness.current, harness.adapter);\n\n    verify(harness.adapter).setDrawable(any(TransitionDrawable.class));\n  }\n\n  @Test\n  public void transition_withNullPreviousDrawable_setsTransitionDrawable() {\n    harness.animation.transition(harness.current, harness.adapter);\n\n    verify(harness.adapter).setDrawable(any(TransitionDrawable.class));\n  }\n\n  @Test\n  public void transition_withNoCurrentDrawable_returnsTrue() {\n    assertTrue(harness.animation.transition(harness.current, harness.adapter));\n  }\n\n  @Test\n  public void transition_withCurrentDrawable_returnsTrue() {\n    Drawable previous = new ColorDrawable(Color.RED);\n    when(harness.adapter.getCurrentDrawable()).thenReturn(previous);\n    assertTrue(harness.animation.transition(harness.current, harness.adapter));\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static class CrossFadeHarness {\n    final Drawable current = new ColorDrawable(Color.GRAY);\n    final ViewAdapter adapter = mock(ViewAdapter.class);\n    final int duration = 200;\n    final DrawableCrossFadeTransition animation =\n        new DrawableCrossFadeTransition(duration, true /*isCrossFadeEnabled*/);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/transition/ViewAnimationTest.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertFalse;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.view.animation.Animation;\nimport android.widget.ImageView;\nimport com.bumptech.glide.request.transition.Transition.ViewAdapter;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ViewAnimationTest {\n  private ViewTransition<Object> viewAnimation;\n  private ViewAdapter adapter;\n  private ImageView view;\n  private ViewTransition.ViewTransitionAnimationFactory viewTransitionAnimationFactory;\n\n  @SuppressWarnings(\"unchecked\")\n  @Before\n  public void setUp() {\n    viewTransitionAnimationFactory = mock(ViewTransition.ViewTransitionAnimationFactory.class);\n    view = mock(ImageView.class);\n    adapter = mock(ViewAdapter.class);\n    when(adapter.getView()).thenReturn(view);\n    viewAnimation = new ViewTransition<>(viewTransitionAnimationFactory);\n  }\n\n  @Test\n  public void testClearsAnimationOnAnimate() {\n    viewAnimation.transition(null, adapter);\n\n    verify(view).clearAnimation();\n  }\n\n  @Test\n  public void testAlwaysReturnsFalse() {\n    assertFalse(viewAnimation.transition(null, adapter));\n  }\n\n  @Test\n  public void testStartsAnimationOnAnimate() {\n    Animation animation = mock(Animation.class);\n    when(viewTransitionAnimationFactory.build(anyContextOrNull())).thenReturn(animation);\n    viewAnimation.transition(null, adapter);\n    verify(view).clearAnimation();\n    verify(view).startAnimation(eq(animation));\n  }\n\n  private static Context anyContextOrNull() {\n    return any();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/transition/ViewPropertyAnimationTest.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertFalse;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.view.View;\nimport android.widget.ImageView;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.request.transition.Transition.ViewAdapter;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ViewPropertyAnimationTest {\n  private ViewPropertyTransition.Animator animator;\n  private ViewPropertyTransition<Object> animation;\n\n  @Before\n  public void setUp() {\n    animator = mock(ViewPropertyTransition.Animator.class);\n    animation = new ViewPropertyTransition<>(animator);\n  }\n\n  @Test\n  public void testAlwaysReturnsFalse() {\n    assertFalse(animation.transition(new Object(), mock(ViewAdapter.class)));\n  }\n\n  @Test\n  public void testCallsAnimatorWithGivenView() {\n    ImageView view = new ImageView(ApplicationProvider.getApplicationContext());\n    ViewAdapter adapter = mock(ViewAdapter.class);\n    when(adapter.getView()).thenReturn(view);\n    animation.transition(new Object(), adapter);\n\n    verify(animator).animate(eq(view));\n  }\n\n  @Test\n  public void testDoesNotCallAnimatorIfGivenAdapterWithNullView() {\n    ViewAdapter adapter = mock(ViewAdapter.class);\n    animation.transition(new Object(), adapter);\n\n    verify(animator, never()).animate(any(View.class));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/transition/ViewPropertyViewTransitionAnimationFactoryTest.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotEquals;\nimport static org.mockito.Mockito.mock;\n\nimport com.bumptech.glide.load.DataSource;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class ViewPropertyViewTransitionAnimationFactoryTest {\n\n  private ViewPropertyAnimationFactory<Object> factory;\n\n  @Before\n  public void setUp() {\n    ViewPropertyTransition.Animator animator = mock(ViewPropertyTransition.Animator.class);\n    factory = new ViewPropertyAnimationFactory<>(animator);\n  }\n\n  @Test\n  public void testReturnsNoAnimationIfFromMemoryCache() {\n    assertEquals(\n        NoTransition.get(), factory.build(DataSource.MEMORY_CACHE, true /*isFirstResource*/));\n  }\n\n  @Test\n  public void testReturnsNoAnimationIfNotFirstResource() {\n    assertEquals(\n        NoTransition.get(), factory.build(DataSource.DATA_DISK_CACHE, false /*isFirstResource*/));\n  }\n\n  @Test\n  public void testReturnsAnimationIfNotFromMemoryCacheAndFirstResource() {\n    assertNotEquals(\n        NoTransition.get(), factory.build(DataSource.DATA_DISK_CACHE, true /*isFirstResource*/));\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/request/transition/ViewTransitionAnimationFactoryTest.java",
    "content": "package com.bumptech.glide.request.transition;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.animation.Animation;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.DataSource;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class ViewTransitionAnimationFactoryTest {\n  private ViewTransition.ViewTransitionAnimationFactory viewTransitionAnimationFactory;\n  private ViewAnimationFactory<Object> factory;\n\n  @Before\n  public void setUp() {\n    viewTransitionAnimationFactory = mock(ViewTransition.ViewTransitionAnimationFactory.class);\n    factory = new ViewAnimationFactory<>(viewTransitionAnimationFactory);\n  }\n\n  @Test\n  public void testFactoryReturnsNoAnimationIfFromMemoryCache() {\n    Transition<Object> animation = factory.build(DataSource.MEMORY_CACHE, true /*isFirstResource*/);\n    assertEquals(NoTransition.get(), animation);\n    verify(viewTransitionAnimationFactory, never())\n        .build(ApplicationProvider.getApplicationContext());\n  }\n\n  @Test\n  public void testFactoryReturnsNoAnimationIfNotFirstResource() {\n    Transition<Object> animation =\n        factory.build(DataSource.DATA_DISK_CACHE, false /*isFirstResource*/);\n    assertEquals(NoTransition.get(), animation);\n    verify(viewTransitionAnimationFactory, never())\n        .build(ApplicationProvider.getApplicationContext());\n  }\n\n  @Test\n  public void testFactoryReturnsActualAnimationIfNotIsFromMemoryCacheAndIsFirstResource() {\n    Transition<Object> transition =\n        factory.build(DataSource.DATA_DISK_CACHE, true /*isFirstResource*/);\n\n    Animation animation = mock(Animation.class);\n    when(viewTransitionAnimationFactory.build(anyContextOrNull())).thenReturn(animation);\n\n    Transition.ViewAdapter adapter = mock(Transition.ViewAdapter.class);\n    View view = mock(View.class);\n    when(adapter.getView()).thenReturn(view);\n    transition.transition(new Object(), adapter);\n\n    verify(view).startAnimation(eq(animation));\n  }\n\n  private static Context anyContextOrNull() {\n    return any();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/resize/load/ExifTest.java",
    "content": "package com.bumptech.glide.resize.load;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\n\nimport com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool;\nimport com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser;\nimport com.bumptech.glide.testutil.TestResourceUtil;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ExifTest {\n\n  private ArrayPool byteArrayPool;\n\n  private InputStream open(String imageName) {\n    return TestResourceUtil.openResource(getClass(), imageName);\n  }\n\n  private void assertOrientation(String filePrefix, int expectedOrientation) {\n    InputStream is = null;\n    try {\n      is = open(filePrefix + \"_\" + expectedOrientation + \".jpg\");\n      assertEquals(\n          new DefaultImageHeaderParser().getOrientation(is, byteArrayPool), expectedOrientation);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    } finally {\n      if (is != null) {\n        try {\n          is.close();\n        } catch (IOException e) {\n          // Do nothing.\n        }\n      }\n    }\n  }\n\n  @Before\n  public void setUp() {\n    byteArrayPool = new LruArrayPool();\n  }\n\n  @Test\n  public void testIssue387() throws IOException {\n    InputStream is = TestResourceUtil.openResource(getClass(), \"issue387_rotated_jpeg.jpg\");\n    assertThat(new DefaultImageHeaderParser().getOrientation(is, byteArrayPool)).isEqualTo(6);\n  }\n\n  @Test\n  public void testLandscape() throws IOException {\n    for (int i = 1; i <= 8; i++) {\n      assertOrientation(\"Landscape\", i);\n    }\n  }\n\n  @Test\n  public void testPortrait() throws IOException {\n    for (int i = 1; i <= 8; i++) {\n      assertOrientation(\"Portrait\", i);\n    }\n  }\n\n  @Test\n  public void testHandlesInexactSizesInByteArrayPools() {\n    for (int i = 1; i <= 8; i++) {\n      byteArrayPool.put(new byte[ArrayPool.STANDARD_BUFFER_SIZE_BYTES]);\n      assertOrientation(\"Portrait\", i);\n    }\n    for (int i = 1; i <= 8; i++) {\n      byteArrayPool.put(new byte[ArrayPool.STANDARD_BUFFER_SIZE_BYTES]);\n      assertOrientation(\"Landscape\", i);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/signature/AndroidResourceSignatureTest.java",
    "content": "package com.bumptech.glide.signature;\n\nimport static org.junit.Assert.assertNotNull;\n\nimport android.content.Context;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.tests.KeyTester;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.RuntimeEnvironment;\nimport org.robolectric.Shadows;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = 28)\npublic class AndroidResourceSignatureTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n  private Context context;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @Test\n  public void testCanGetKeyForSignature() {\n    Key key = AndroidResourceSignature.obtain(context);\n    assertNotNull(key);\n  }\n\n  @Test\n  public void testKeyForSignatureIsTheSameAcrossCallsInTheSamePackage() {\n    keyTester\n        .addEquivalenceGroup(\n            AndroidResourceSignature.obtain(context), AndroidResourceSignature.obtain(context))\n        .addEquivalenceGroup(new ObjectKey(\"test\"))\n        .addRegressionTest(\n            ApplicationVersionSignature.obtain(context),\n            \"5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\")\n        .test();\n  }\n\n  @Test\n  public void testKeyForSignatureDiffersByNightMode() {\n    RuntimeEnvironment.setQualifiers(\"notnight\");\n    keyTester\n        .addEquivalenceGroup(\n            AndroidResourceSignature.obtain(context), AndroidResourceSignature.obtain(context))\n        .addRegressionTest(\n            AndroidResourceSignature.obtain(context),\n            \"265d958bdae1bea56e45cc31f4db672c22893b66fef85617bbc78742bd912207\");\n    RuntimeEnvironment.setQualifiers(\"night\");\n    keyTester\n        .addEquivalenceGroup(\n            AndroidResourceSignature.obtain(context), AndroidResourceSignature.obtain(context))\n        .addRegressionTest(\n            AndroidResourceSignature.obtain(context),\n            \"96c9b8d5bb071ccd67df50cd9a0059640ebd02db78d08f07611ec145ce44a638\");\n\n    keyTester.test();\n  }\n\n  @Test\n  public void testMissingPackageInfo() throws NameNotFoundException {\n    // Make getPackageInfo throw NameNotFoundException.\n    Shadows.shadowOf(context.getPackageManager()).removePackage(context.getPackageName());\n    Key key = AndroidResourceSignature.obtain(context);\n\n    assertNotNull(key);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/signature/ApplicationVersionSignatureTest.java",
    "content": "package com.bumptech.glide.signature;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport android.content.Context;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.tests.KeyTester;\nimport java.io.UnsupportedEncodingException;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Answers;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ApplicationVersionSignatureTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n  private Context context;\n\n  @Before\n  public void setUp() {\n    context = ApplicationProvider.getApplicationContext();\n  }\n\n  @After\n  public void tearDown() {\n    ApplicationVersionSignature.reset();\n  }\n\n  @Test\n  public void testCanGetKeyForSignature() {\n    Key key = ApplicationVersionSignature.obtain(context);\n    assertNotNull(key);\n  }\n\n  @Test\n  public void testKeyForSignatureIsTheSameAcrossCallsInTheSamePackage()\n      throws NoSuchAlgorithmException, UnsupportedEncodingException {\n    keyTester\n        .addEquivalenceGroup(\n            ApplicationVersionSignature.obtain(context),\n            ApplicationVersionSignature.obtain(context))\n        .addEquivalenceGroup(new ObjectKey(\"test\"))\n        .addRegressionTest(\n            ApplicationVersionSignature.obtain(context),\n            \"5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9\")\n        .test();\n  }\n\n  @Test\n  public void testUnresolvablePackageInfo() throws NameNotFoundException {\n    Context context = mock(Context.class, Answers.RETURNS_DEEP_STUBS);\n    String packageName = \"my.package\";\n    when(context.getPackageName()).thenReturn(packageName);\n    when(context.getPackageManager().getPackageInfo(packageName, 0))\n        .thenThrow(new NameNotFoundException(\"test\"));\n\n    Key key = ApplicationVersionSignature.obtain(context);\n\n    assertNotNull(key);\n  }\n\n  @Test\n  public void testMissingPackageInfo() throws NameNotFoundException {\n    Context context = mock(Context.class, Answers.RETURNS_DEEP_STUBS);\n    String packageName = \"my.package\";\n    when(context.getPackageName()).thenReturn(packageName);\n    when(context.getPackageManager().getPackageInfo(packageName, 0)).thenReturn(null);\n\n    Key key = ApplicationVersionSignature.obtain(context);\n\n    assertNotNull(key);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/signature/EmptySignatureTest.java",
    "content": "package com.bumptech.glide.signature;\n\nimport static org.mockito.Mockito.mock;\n\nimport com.bumptech.glide.load.Key;\nimport com.bumptech.glide.tests.KeyTester;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class EmptySignatureTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Test\n  public void testEquals() {\n    keyTester\n        .addEquivalenceGroup(EmptySignature.obtain(), EmptySignature.obtain())\n        .addEquivalenceGroup(mock(Key.class))\n        .addEmptyDigestRegressionTest(EmptySignature.obtain())\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/signature/MediaStoreSignatureTest.java",
    "content": "package com.bumptech.glide.signature;\n\nimport com.bumptech.glide.tests.KeyTester;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class MediaStoreSignatureTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Test\n  public void equalsHashCodeAndDigest() {\n    keyTester\n        .addEquivalenceGroup(\n            new MediaStoreSignature(\"first\", 100, 1), new MediaStoreSignature(\"first\", 100, 1))\n        .addEquivalenceGroup(new MediaStoreSignature(\"second\", 100, 1))\n        .addEquivalenceGroup(new MediaStoreSignature(\"first\", 200, 1))\n        .addEquivalenceGroup(new MediaStoreSignature(\"first\", 100, 2))\n        .addRegressionTest(\n            new MediaStoreSignature(\"first\", 100, 1),\n            \"04959925006b21081000fd10835cc376343c0e922df0bd7346897ede6f958adf\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/signature/ObjectKeyTest.java",
    "content": "package com.bumptech.glide.signature;\n\nimport com.bumptech.glide.tests.KeyTester;\nimport java.security.NoSuchAlgorithmException;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class ObjectKeyTest {\n  @Rule public final KeyTester keyTester = new KeyTester();\n\n  @Test\n  public void testEqualsHashCodeAndDigest() throws NoSuchAlgorithmException {\n    Object object = new Object();\n    keyTester\n        .addEquivalenceGroup(new ObjectKey(object), new ObjectKey(object))\n        .addEquivalenceGroup(new ObjectKey(new Object()))\n        .addEquivalenceGroup(new ObjectKey(\"test\"), new ObjectKey(\"test\"))\n        .addRegressionTest(\n            new ObjectKey(\"test\"),\n            \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\")\n        .test();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/tests/BackgroundUtil.java",
    "content": "package com.bumptech.glide.tests;\n\npublic final class BackgroundUtil {\n\n  private BackgroundUtil() {\n    // Utility class.\n  }\n\n  public static void testInBackground(BackgroundTester test) throws InterruptedException {\n    TestThread thread = new TestThread(test);\n    thread.start();\n    thread.join();\n    if (thread.exception != null) {\n      throw new RuntimeException(thread.exception);\n    }\n  }\n\n  private static final class TestThread extends Thread {\n    private final BackgroundTester test;\n    private Exception exception;\n\n    private TestThread(BackgroundTester test) {\n      this.test = test;\n    }\n\n    @Override\n    public void run() {\n      super.run();\n      try {\n        test.runTest();\n      } catch (Exception e) {\n        exception = e;\n      }\n    }\n  }\n\n  public interface BackgroundTester {\n    void runTest();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/tests/ContentResolverShadow.java",
    "content": "package com.bumptech.glide.tests;\n\nimport android.content.ContentResolver;\nimport android.content.res.AssetFileDescriptor;\nimport android.net.Uri;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\n\n@Implements(ContentResolver.class)\npublic class ContentResolverShadow {\n  private final Map<Uri, AssetFileDescriptor> fileDescriptorMap = new HashMap<>();\n  private final Map<Uri, InputStream> inputStreamMap = new HashMap<>();\n\n  public void registerFileDescriptor(Uri uri, AssetFileDescriptor fileDescriptor) {\n    fileDescriptorMap.put(uri, fileDescriptor);\n  }\n\n  public void registerInputStream(Uri uri, InputStream inputStream) {\n    inputStreamMap.put(uri, inputStream);\n  }\n\n  @Implementation\n  public InputStream openInputStream(Uri uri) {\n    return inputStreamMap.get(uri);\n  }\n\n  @Implementation\n  public AssetFileDescriptor openAssetFileDescriptor(Uri uri, String mode) {\n    return fileDescriptorMap.get(uri);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/tests/GlideShadowLog.java",
    "content": "package com.bumptech.glide.tests;\n\nimport android.util.Log;\nimport org.robolectric.annotation.Implementation;\nimport org.robolectric.annotation.Implements;\nimport org.robolectric.shadows.ShadowLog;\n\n/**\n * Exists only to \"enable\" logging for test coverage.\n *\n * <p>TODO: when we can ignore Log.* via configuration, remove this class.\n */\n@Implements(Log.class)\npublic class GlideShadowLog extends ShadowLog {\n\n  @Implementation\n  public static boolean isLoggable(String tag, int level) {\n    return true;\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/tests/KeyTester.java",
    "content": "package com.bumptech.glide.tests;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth.assert_;\nimport static org.junit.Assert.fail;\n\nimport androidx.annotation.CheckResult;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Key;\nimport com.google.common.base.Equivalence;\nimport com.google.common.testing.EquivalenceTester;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\npublic final class KeyTester implements TestRule {\n  private static final String EMPTY_DIGEST_STRING =\n      \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\";\n  private final List<KeyAndHash> regressionTests = new ArrayList<>();\n  private final Sha256 sha256 = new Sha256();\n  private final EquivalenceTester<Key> tester = EquivalenceTester.of(new KeyEquivalence(sha256));\n  private boolean isUsedWithoutCallingTest;\n  private boolean isUsedAsRule;\n\n  @Override\n  public Statement apply(final Statement base, Description description) {\n    return new Statement() {\n\n      @Override\n      public void evaluate() throws Throwable {\n        isUsedAsRule = true;\n        base.evaluate();\n        if (isUsedWithoutCallingTest) {\n          fail(\"You used KeyTester but failed to call test()!\");\n        }\n      }\n    };\n  }\n\n  private void assertUsedAsRule() {\n    if (!isUsedAsRule) {\n      fail(\"You must use KeyTester as an @Rule\");\n    }\n  }\n\n  @CheckResult\n  public KeyTester addEquivalenceGroup(Key first, Key... rest) {\n    assertUsedAsRule();\n    isUsedWithoutCallingTest = true;\n    tester.addEquivalenceGroup(first, rest);\n    return this;\n  }\n\n  @CheckResult\n  public KeyTester addRegressionTest(Key key, String expectedDigest) {\n    assertUsedAsRule();\n    if (EMPTY_DIGEST_STRING.equals(expectedDigest)) {\n      throw new IllegalArgumentException(\n          \"Expected digest is empty, if this is intended use \"\n              + \"addEmptyDigestRegressionTest instead\");\n    }\n    return addRegressionTestInternal(key, expectedDigest);\n  }\n\n  @CheckResult\n  public KeyTester addEmptyDigestRegressionTest(Key key) {\n    assertUsedAsRule();\n    return addRegressionTestInternal(key, EMPTY_DIGEST_STRING);\n  }\n\n  private KeyTester addRegressionTestInternal(Key key, String expectedDigest) {\n    isUsedWithoutCallingTest = true;\n    regressionTests.add(new KeyAndHash(key, expectedDigest));\n    return this;\n  }\n\n  public void test() {\n    assertUsedAsRule();\n    isUsedWithoutCallingTest = false;\n    tester.test();\n\n    assertThat(regressionTests).isNotEmpty();\n    int i = 1;\n    for (KeyAndHash keyAndHash : regressionTests) {\n      assert_()\n          .withMessage(\n              \"Unexpected digest for regression test [\" + i + \"]: with key: \" + keyAndHash.key)\n          .that(sha256.getStringDigest(keyAndHash.key))\n          .isEqualTo(keyAndHash.hash);\n      i++;\n    }\n  }\n\n  private static final class Sha256 {\n\n    private final MessageDigest digest;\n\n    Sha256() {\n      try {\n        digest = MessageDigest.getInstance(\"SHA-256\");\n      } catch (NoSuchAlgorithmException e) {\n        throw new RuntimeException(e);\n      }\n    }\n\n    private byte[] getDigest(Key key) {\n      try {\n        key.updateDiskCacheKey(digest);\n        return digest.digest();\n      } finally {\n        digest.reset();\n      }\n    }\n\n    String getStringDigest(Key key) {\n      return com.bumptech.glide.util.Util.sha256BytesToHex(getDigest(key));\n    }\n  }\n\n  /** Tests equals, hashcode and digest methods of {@link Key}s. */\n  private static final class KeyEquivalence extends Equivalence<Key> {\n\n    private final Sha256 sha256;\n\n    KeyEquivalence(Sha256 sha256) {\n      this.sha256 = sha256;\n    }\n\n    @Override\n    protected boolean doEquivalent(@NonNull Key a, @NonNull Key b) {\n      byte[] aDigest = sha256.getDigest(a);\n      byte[] bDigest = sha256.getDigest(b);\n      Object object = new Object();\n      Object sentinel = null;\n      return a.equals(b)\n          && b.equals(a)\n          && !a.equals(sentinel)\n          && !b.equals(sentinel)\n          && !a.equals(object)\n          && !b.equals(object)\n          && Arrays.equals(aDigest, bDigest);\n    }\n\n    @Override\n    protected int doHash(@NonNull Key key) {\n      return key.hashCode();\n    }\n  }\n\n  private static final class KeyAndHash {\n    private final Key key;\n    private final String hash;\n\n    KeyAndHash(Key key, String hash) {\n      this.key = key;\n      this.hash = hash;\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/tests/TearDownGlide.java",
    "content": "package com.bumptech.glide.tests;\n\nimport com.bumptech.glide.Glide;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\n/** Clears out Glide's disk cache and the Glide singleton after every test method. */\npublic final class TearDownGlide implements TestRule {\n  @Override\n  public Statement apply(final Statement base, Description description) {\n    return new Statement() {\n      @Override\n      public void evaluate() throws Throwable {\n        try {\n          base.evaluate();\n        } finally {\n          Glide.tearDown();\n        }\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/tests/Util.java",
    "content": "package com.bumptech.glide.tests;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.RETURNS_DEFAULTS;\nimport static org.mockito.Mockito.mock;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.engine.Resource;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.security.MessageDigest;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.robolectric.util.ReflectionHelpers;\n\n// FIXME move to testutil module\npublic class Util {\n\n  /**\n   * Gives the proper generic type to the {@link ArgumentCaptor}. Only useful when the captor's\n   * {@code T} is also a generic type. Without this it's really ugly to have a properly typed captor\n   * object.\n   */\n  @SuppressWarnings(\"unchecked\")\n  public static <T> ArgumentCaptor<T> cast(ArgumentCaptor<?> captor) {\n    return (ArgumentCaptor<T>) captor;\n  }\n\n  public static DataSource isADataSource() {\n    return isA(DataSource.class);\n  }\n\n  public static Context anyContext() {\n    return any();\n  }\n\n  /**\n   * Creates a Mockito argument matcher to be used in verify. It returns a generic typed {@link\n   * Resource} to prevent unchecked warnings.\n   */\n  @SuppressWarnings(\"unchecked\")\n  public static <T> Resource<T> anyResource() {\n    return any(Resource.class);\n  }\n\n  /**\n   * Creates a Mockito mock object. It returns a generic typed {@link Resource} to prevent unchecked\n   * warnings.\n   */\n  @SuppressWarnings(\"unchecked\")\n  public static <T> Resource<T> mockResource() {\n    return mock(Resource.class);\n  }\n\n  public static boolean isWindows() {\n    return System.getProperty(\"os.name\").startsWith(\"Windows\");\n  }\n\n  public static void writeFile(File file, byte[] data) throws IOException {\n    OutputStream out = new FileOutputStream(file);\n    try {\n      out.write(data);\n      out.flush();\n      out.close();\n    } finally {\n      try {\n        out.close();\n      } catch (IOException ex) {\n        // Do nothing.\n      }\n    }\n  }\n\n  public static byte[] readFile(File file, int expectedLength) throws IOException {\n    InputStream is = new FileInputStream(file);\n    byte[] result = new byte[expectedLength];\n    try {\n      assertEquals(expectedLength, is.read(result));\n      assertEquals(-1, is.read());\n    } finally {\n      try {\n        is.close();\n      } catch (IOException e) {\n        // Do nothing.\n      }\n    }\n    return result;\n  }\n\n  public static void setSdkVersionInt(int version) {\n    ReflectionHelpers.setStaticField(Build.VERSION.class, \"SDK_INT\", version);\n  }\n\n  public static final class WriteDigest implements Answer<Void> {\n    private final String toWrite;\n\n    public WriteDigest(String toWrite) {\n      this.toWrite = toWrite;\n    }\n\n    @Override\n    public Void answer(InvocationOnMock invocationOnMock) throws Throwable {\n      MessageDigest md = (MessageDigest) invocationOnMock.getArguments()[0];\n      md.update(toWrite.getBytes(\"UTF-8\"));\n      return null;\n    }\n  }\n\n  public static final class ReturnsSelfAnswer implements Answer<Object> {\n\n    @Override\n    public Object answer(InvocationOnMock invocation) throws Throwable {\n      Object mock = invocation.getMock();\n      if (invocation.getMethod().getReturnType().isInstance(mock)) {\n        return mock;\n      } else {\n        return RETURNS_DEFAULTS.answer(invocation);\n      }\n    }\n  }\n\n  public static final class CallDataReady<T> implements Answer<Void> {\n\n    private final T data;\n\n    public CallDataReady(T data) {\n      this.data = data;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public Void answer(InvocationOnMock invocationOnMock) throws Throwable {\n      DataFetcher.DataCallback<T> callback =\n          (DataFetcher.DataCallback<T>) invocationOnMock.getArguments()[1];\n      callback.onDataReady(data);\n      return null;\n    }\n  }\n\n  public static final class CreateBitmap implements Answer<Bitmap> {\n\n    @Override\n    public Bitmap answer(InvocationOnMock invocation) throws Throwable {\n      int width = (Integer) invocation.getArguments()[0];\n      int height = (Integer) invocation.getArguments()[1];\n      Bitmap.Config config = (Bitmap.Config) invocation.getArguments()[2];\n      return Bitmap.createBitmap(width, height, config);\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/util/ByteBufferUtilTest.java",
    "content": "package com.bumptech.glide.util;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static org.junit.Assert.assertEquals;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ByteBufferUtilTest {\n  private static final int BUFFER_SIZE = 16384;\n\n  @Test\n  public void testFromStream_small() throws IOException {\n    testFromStream(4);\n  }\n\n  @Test\n  public void testFromStream_empty() throws IOException {\n    testFromStream(0);\n  }\n\n  @Test\n  public void testFromStream_bufferAndAHalf() throws IOException {\n    testFromStream(BUFFER_SIZE + BUFFER_SIZE / 2);\n  }\n\n  @Test\n  public void testFromStream_massive() throws IOException {\n    testFromStream(12 * BUFFER_SIZE + 12345);\n  }\n\n  /** All tests are basically the same thing but with different amounts of data. */\n  private void testFromStream(int dataLength) throws IOException {\n    byte[] bytes = createByteData(dataLength);\n    InputStream byteStream = new ByteArrayInputStream(bytes);\n    ByteBuffer byteBuffer = ByteBufferUtil.fromStream(byteStream);\n    assertByteBufferContents(byteBuffer, bytes);\n    byteStream.close();\n  }\n\n  private byte[] createByteData(int size) {\n    byte[] bytes = new byte[size];\n\n    // Put some arbitrary bytes in there.\n    for (int i = 0; i < size; i++) {\n      bytes[i] = (byte) (i % 4);\n    }\n\n    return bytes;\n  }\n\n  private void assertByteBufferContents(ByteBuffer buffer, byte[] expectedBytes) {\n    assertEquals(expectedBytes.length, buffer.limit());\n    for (int i = 0; i < expectedBytes.length; i++) {\n      assertEquals(expectedBytes[i], buffer.get(i));\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/util/ContentLengthInputStreamTest.java",
    "content": "package com.bumptech.glide.util;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.fail;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.Mockito.when;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class ContentLengthInputStreamTest {\n  @Mock private InputStream wrapped;\n\n  @Before\n  public void setUp() {\n    MockitoAnnotations.initMocks(this);\n  }\n\n  @Test\n  public void testAvailable_withZeroReadsAndValidContentLength_returnsContentLength()\n      throws IOException {\n    int value = 123356;\n    InputStream is = ContentLengthInputStream.obtain(wrapped, String.valueOf(value));\n\n    assertThat(is.available()).isEqualTo(value);\n  }\n\n  @Test\n  public void testAvailable_withNullContentLength_returnsWrappedAvailable() throws IOException {\n    InputStream is = ContentLengthInputStream.obtain(wrapped, null /*contentLengthHeader*/);\n    int expected = 1234;\n    when(wrapped.available()).thenReturn(expected);\n\n    assertThat(is.available()).isEqualTo(expected);\n  }\n\n  @Test\n  public void testAvailable_withInvalidContentLength_returnsWrappedAvailable() throws IOException {\n    InputStream is = ContentLengthInputStream.obtain(wrapped, \"invalid_length\");\n    int expected = 567;\n    when(wrapped.available()).thenReturn(expected);\n\n    assertThat(is.available()).isEqualTo(expected);\n  }\n\n  @Test\n  public void testAvailable_withRead_returnsContentLengthOffsetByRead() throws IOException {\n    int contentLength = 999;\n    InputStream is = ContentLengthInputStream.obtain(wrapped, String.valueOf(contentLength));\n    when(wrapped.read()).thenReturn(1);\n\n    assertThat(is.read()).isEqualTo(1);\n    assertThat(is.available()).isEqualTo(contentLength - 1);\n  }\n\n  @Test\n  public void testAvailable_handlesReadValueOfZero() throws IOException {\n    int contentLength = 999;\n    InputStream is = ContentLengthInputStream.obtain(wrapped, String.valueOf(contentLength));\n    when(wrapped.read()).thenReturn(0);\n\n    assertThat(is.read()).isEqualTo(0);\n    assertThat(is.available()).isEqualTo(contentLength - 1);\n  }\n\n  @Test\n  public void testAvailable_withReadBytes_returnsContentLengthOffsetByNumberOfBytes()\n      throws IOException {\n    int contentLength = 678;\n    InputStream is = ContentLengthInputStream.obtain(wrapped, String.valueOf(contentLength));\n    int read = 100;\n    when(wrapped.read(any(byte[].class), anyInt(), anyInt())).thenReturn(read);\n\n    assertThat(is.read(new byte[500], 0, 0)).isEqualTo(read);\n    assertThat(is.available()).isEqualTo(contentLength - read);\n  }\n\n  @Test\n  public void testRead_whenReturnsLessThanZeroWithoutReadingAllContent_throwsIOException()\n      throws IOException {\n    int contentLength = 1;\n    InputStream is = ContentLengthInputStream.obtain(wrapped, String.valueOf(contentLength));\n    when(wrapped.read()).thenReturn(-1);\n\n    try {\n      //noinspection ResultOfMethodCallIgnored\n      is.read();\n      fail(\"Failed to throw expected exception\");\n    } catch (IOException e) {\n      // Expected.\n    }\n  }\n\n  @Test\n  public void testReadBytes_whenReturnsLessThanZeroWithoutReadingAllContent_throwsIOException()\n      throws IOException {\n    int contentLength = 2;\n    InputStream is = ContentLengthInputStream.obtain(wrapped, String.valueOf(contentLength));\n    when(wrapped.read(any(byte[].class), anyInt(), anyInt())).thenReturn(-1);\n\n    try {\n      //noinspection ResultOfMethodCallIgnored\n      is.read(new byte[10], 0, 0);\n      fail(\"Failed to throw expected exception\");\n    } catch (IOException e) {\n      // Expected.\n    }\n  }\n\n  @Test\n  public void testRead_whenReturnsLessThanZeroWithInvalidLength_doesNotThrow() throws IOException {\n    InputStream is = ContentLengthInputStream.obtain(wrapped, \"invalid_length\");\n    when(wrapped.read()).thenReturn(-1);\n    //noinspection ResultOfMethodCallIgnored\n    is.read();\n  }\n\n  @Test\n  public void testReadBytes_whenReturnsLessThanZeroWithInvalidLength_doesNotThrow()\n      throws IOException {\n    InputStream is = ContentLengthInputStream.obtain(wrapped, \"invalid_length\");\n    when(wrapped.read(any(byte[].class), anyInt(), anyInt())).thenReturn(-1);\n    //noinspection ResultOfMethodCallIgnored\n    is.read(new byte[10], 0, 0);\n  }\n\n  @Test\n  public void testRead_readWithZeroes_doesNotThrow() throws IOException {\n    ByteArrayInputStream inner = new ByteArrayInputStream(new byte[] {0, 0, 0});\n    InputStream is = ContentLengthInputStream.obtain(inner, 3);\n\n    assertThat(is.read()).isEqualTo(0);\n    assertThat(is.read()).isEqualTo(0);\n    assertThat(is.read()).isEqualTo(0);\n    assertThat(is.read()).isEqualTo(-1);\n  }\n\n  @Test\n  public void testRead_readWithHighValues_doesNotThrow() throws IOException {\n    ByteArrayInputStream inner =\n        new ByteArrayInputStream(new byte[] {(byte) 0xF0, (byte) 0xA0, (byte) 0xFF});\n    InputStream is = ContentLengthInputStream.obtain(inner, 3);\n\n    assertThat(is.read()).isEqualTo(0xF0);\n    assertThat(is.read()).isEqualTo(0xA0);\n    assertThat(is.read()).isEqualTo(0xFF);\n    assertThat(is.read()).isEqualTo(-1);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/util/ExceptionPassthroughInputStreamTest.java",
    "content": "package com.bumptech.glide.util;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.assertTrue;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.SocketTimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.function.ThrowingRunnable;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic class ExceptionPassthroughInputStreamTest {\n\n  private final InputStream validInputStream =\n      new ByteArrayInputStream(\n          new byte[] {\n            0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n          });\n  private final InputStream throwingInputStream = new ExceptionThrowingInputStream();\n  private ExceptionPassthroughInputStream validWrapper;\n  private ExceptionPassthroughInputStream throwingWrapper;\n\n  @Before\n  public void setUp() throws Exception {\n    validWrapper = new ExceptionPassthroughInputStream();\n    validWrapper.setInputStream(validInputStream);\n    throwingWrapper = new ExceptionPassthroughInputStream();\n    throwingWrapper.setInputStream(throwingInputStream);\n  }\n\n  @After\n  public void tearDown() {\n    ExceptionPassthroughInputStream.clearQueue();\n  }\n\n  @Test\n  public void testReturnsWrappedAvailable() throws IOException {\n    assertEquals(validInputStream.available(), validWrapper.available());\n  }\n\n  @Test\n  public void testCallsCloseOnWrapped() throws IOException {\n    ExceptionPassthroughInputStream wrapper = new ExceptionPassthroughInputStream();\n    final AtomicBoolean isClosed = new AtomicBoolean();\n    wrapper.setInputStream(\n        new InputStream() {\n          @Override\n          public int read() {\n            return 0;\n          }\n\n          @Override\n          public void close() throws IOException {\n            super.close();\n            isClosed.set(true);\n          }\n        });\n    wrapper.close();\n    assertThat(isClosed.get()).isTrue();\n  }\n\n  @Test\n  public void testCallsMarkOnWrapped() throws IOException {\n    int toMark = 5;\n    validWrapper.mark(toMark);\n    assertThat(validWrapper.read(new byte[5], 0, 5)).isEqualTo(5);\n    validInputStream.reset();\n    assertThat(validInputStream.read()).isEqualTo(0);\n  }\n\n  @Test\n  public void testReturnsWrappedMarkSupported() {\n    assertTrue(validWrapper.markSupported());\n  }\n\n  @Test\n  public void testCallsReadByteArrayOnWrapped() throws IOException {\n    byte[] buffer = new byte[8];\n    assertEquals(buffer.length, validWrapper.read(buffer));\n  }\n\n  @Test\n  public void testCallsReadArrayWithOffsetAndCountOnWrapped() throws IOException {\n    int offset = 1;\n    int count = 4;\n    byte[] buffer = new byte[5];\n\n    assertEquals(count, validWrapper.read(buffer, offset, count));\n  }\n\n  @Test\n  public void testCallsReadOnWrapped() throws IOException {\n    assertEquals(0, validWrapper.read());\n    assertEquals(1, validWrapper.read());\n    assertEquals(2, validWrapper.read());\n  }\n\n  @Test\n  public void testCallsResetOnWrapped() throws IOException {\n    validWrapper.mark(5);\n    assertThat(validWrapper.read()).isEqualTo(0);\n    assertThat(validWrapper.read()).isEqualTo(1);\n    validWrapper.reset();\n    assertThat(validWrapper.read()).isEqualTo(0);\n  }\n\n  @Test\n  public void testCallsSkipOnWrapped() throws IOException {\n    int toSkip = 5;\n    assertThat(validWrapper.skip(toSkip)).isEqualTo(toSkip);\n    assertThat(validWrapper.read()).isEqualTo(5);\n  }\n\n  @Test\n  public void testCatchesExceptionOnRead() {\n    SocketTimeoutException expected =\n        assertThrows(\n            SocketTimeoutException.class,\n            new ThrowingRunnable() {\n              @Override\n              public void run() throws Throwable {\n                throwingWrapper.read();\n              }\n            });\n    assertEquals(expected, throwingWrapper.getException());\n  }\n\n  @Test\n  public void testCatchesExceptionOnReadBuffer() {\n    SocketTimeoutException exception =\n        assertThrows(\n            SocketTimeoutException.class,\n            new ThrowingRunnable() {\n              @Override\n              public void run() throws Throwable {\n                throwingWrapper.read(new byte[1]);\n              }\n            });\n    assertEquals(exception, throwingWrapper.getException());\n  }\n\n  @Test\n  public void testCatchesExceptionOnReadBufferWithOffsetAndCount() {\n    SocketTimeoutException exception =\n        assertThrows(\n            SocketTimeoutException.class,\n            new ThrowingRunnable() {\n              @Override\n              public void run() throws Throwable {\n                throwingWrapper.read(new byte[2], 1, 1);\n              }\n            });\n    assertEquals(exception, throwingWrapper.getException());\n  }\n\n  @Test\n  public void testCatchesExceptionOnSkip() {\n    SocketTimeoutException exception =\n        assertThrows(\n            SocketTimeoutException.class,\n            new ThrowingRunnable() {\n              @Override\n              public void run() throws Throwable {\n                throwingWrapper.skip(100);\n              }\n            });\n    assertEquals(exception, throwingWrapper.getException());\n  }\n\n  @Test\n  public void testExceptionIsNotSetInitially() {\n    assertNull(validWrapper.getException());\n  }\n\n  @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n  @Test\n  public void testResetsExceptionToNullOnRelease() {\n    assertThrows(\n        SocketTimeoutException.class,\n        new ThrowingRunnable() {\n          @Override\n          public void run() throws Throwable {\n            throwingWrapper.read();\n          }\n        });\n    throwingWrapper.release();\n    assertNull(validWrapper.getException());\n  }\n\n  @Test\n  public void testCanReleaseAnObtainFromPool() {\n    validWrapper.release();\n    InputStream fromPool = ExceptionPassthroughInputStream.obtain(validInputStream);\n    assertEquals(validWrapper, fromPool);\n  }\n\n  @Test\n  public void testCanObtainNewStreamFromPool() throws IOException {\n    InputStream fromPool = ExceptionPassthroughInputStream.obtain(validInputStream);\n    int read = fromPool.read();\n    assertEquals(0, read);\n  }\n\n  private static final class ExceptionThrowingInputStream extends InputStream {\n    @Override\n    public int read() throws IOException {\n      throw new SocketTimeoutException();\n    }\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/util/FixedPreloadSizeProviderTest.java",
    "content": "package com.bumptech.glide.util;\n\nimport static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;\nimport static com.google.common.truth.Truth.assertThat;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = ROBOLECTRIC_SDK)\npublic class FixedPreloadSizeProviderTest {\n\n  // containsExactly doesn't need a return value check.\n  @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n  @Test\n  public void testReturnsGivenSize() {\n    int width = 500;\n    int height = 1234;\n    FixedPreloadSizeProvider<Object> provider = new FixedPreloadSizeProvider<>(width, height);\n\n    int[] size = provider.getPreloadSize(new Object(), 0, 0);\n\n    assertThat(size).asList().containsExactly(width, height);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/util/MarkEnforcingInputStreamTest.java",
    "content": "package com.bumptech.glide.util;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\n\n@RunWith(RobolectricTestRunner.class)\npublic class MarkEnforcingInputStreamTest {\n  // An arbitrary number > 0.\n  private static final int MARK_LIMIT = 5;\n  // Another arbitrary number > MARK_LIMIT.\n  private static final int DATA_SIZE = MARK_LIMIT + 1;\n\n  @Test\n  public void testReturnsByte_whenReadsUpToMarkLimit_withMoreBytesAvailable() throws IOException {\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[DATA_SIZE]));\n    is.mark(MARK_LIMIT);\n\n    for (int i = 0; i < MARK_LIMIT; i++) {\n      assertThat(is.read()).isAtLeast(0);\n    }\n  }\n\n  @Test\n  public void testReturnsByte_whenReadsUpToMarkLimit_withNoMoreBytesAvailable() throws IOException {\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[MARK_LIMIT]));\n\n    for (int i = 0; i < MARK_LIMIT; i++) {\n      assertThat(is.read()).isAtLeast(0);\n    }\n  }\n\n  @Test\n  public void testReturnsEndOfStream_whenReadsSingleBytePastMarkLimit() throws IOException {\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[DATA_SIZE]));\n\n    is.mark(MARK_LIMIT);\n    for (int i = 0; i < MARK_LIMIT; i++) {\n      assertThat(is.read()).isAtLeast(0);\n    }\n\n    assertEquals(-1, is.read());\n  }\n\n  @Test\n  public void\n      testOverridesByteCount_whenReadBufferLargerThanMarkLimit_withNonZeroBytesRemainingInMarkLimit()\n          throws IOException {\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[DATA_SIZE]));\n\n    is.mark(MARK_LIMIT);\n    byte[] buffer = new byte[DATA_SIZE];\n    assertEquals(MARK_LIMIT, is.read(buffer));\n  }\n\n  @Test\n  public void\n      testReturnsEndOfStream_whenReadBufferLargerThanMarkLimit_withZeroBytesRemainingInMarkLimit()\n          throws IOException {\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[DATA_SIZE]));\n    is.mark(MARK_LIMIT);\n\n    byte[] buffer = new byte[MARK_LIMIT];\n    assertEquals(MARK_LIMIT, is.read(buffer));\n    assertEquals(-1, is.read(buffer));\n  }\n\n  @Test\n  public void testDoesNotReadIntoBuffer_withZeroBytesRemainingInMarkLimit() throws IOException {\n    byte[] expected = new byte[MARK_LIMIT];\n    for (int i = 0; i < MARK_LIMIT; i++) {\n      expected[i] = (byte) (i + 1);\n    }\n    byte[] buffer = new byte[MARK_LIMIT];\n    System.arraycopy(expected, 0, buffer, 0, MARK_LIMIT);\n\n    // All zeros.\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[DATA_SIZE]));\n    is.mark(MARK_LIMIT);\n    for (int i = 0; i < MARK_LIMIT; i++) {\n      assertThat(is.read()).isAtLeast(0);\n    }\n\n    assertEquals(-1, is.read(buffer));\n\n    assertThat(buffer).isEqualTo(expected);\n  }\n\n  @Test\n  public void testResetUnsetsLimit() throws IOException {\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[DATA_SIZE]));\n    is.mark(MARK_LIMIT);\n\n    for (int i = 0; i < MARK_LIMIT; i++) {\n      assertThat(is.read()).isAtLeast(0);\n    }\n\n    is.reset();\n\n    for (int i = 0; i < DATA_SIZE; i++) {\n      assertThat(is.read()).isAtLeast(0);\n    }\n  }\n\n  @Test\n  public void\n      testOverridesByteCount_whenSkipCountLargerThanMarkLimit_withNonZeroBytesRemainingInMarkLimit()\n          throws IOException {\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[DATA_SIZE]));\n    is.mark(MARK_LIMIT);\n\n    assertEquals(MARK_LIMIT, is.skip(DATA_SIZE));\n  }\n\n  @Test\n  public void testReturnsEndOfStream_whenSkipping_withZeroBytesRemainingInMarkLimit()\n      throws IOException {\n    MarkEnforcingInputStream is =\n        new MarkEnforcingInputStream(new ByteArrayInputStream(new byte[DATA_SIZE]));\n    is.mark(MARK_LIMIT);\n\n    assertEquals(MARK_LIMIT, is.skip(DATA_SIZE));\n    assertEquals(0, is.skip(1));\n  }\n\n  @Test\n  public void testReturnsStreamAvailable_whenMarkIsNotSet() throws IOException {\n    ByteArrayInputStream wrapped = new ByteArrayInputStream(new byte[MARK_LIMIT]);\n    MarkEnforcingInputStream is = new MarkEnforcingInputStream(wrapped);\n\n    assertEquals(wrapped.available(), is.available());\n  }\n\n  @Test\n  public void testReturnsStreamAvailable_whenMarkIsSet_withMarkGreaterThanStreamAvailable()\n      throws IOException {\n    ByteArrayInputStream wrapped = new ByteArrayInputStream(new byte[MARK_LIMIT]);\n    MarkEnforcingInputStream is = new MarkEnforcingInputStream(wrapped);\n    is.mark(wrapped.available() + 1);\n\n    assertEquals(wrapped.available(), is.available());\n  }\n\n  @Test\n  public void testReturnsMarkLimitAsAvailable_whenMarkIsSet_withMarkLessThanStreamAvailable()\n      throws IOException {\n    ByteArrayInputStream wrapped = new ByteArrayInputStream(new byte[MARK_LIMIT]);\n    MarkEnforcingInputStream is = new MarkEnforcingInputStream(wrapped);\n    int expected = wrapped.available() - 1;\n    is.mark(expected);\n\n    assertEquals(expected, is.available());\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/util/UtilTest.java",
    "content": "package com.bumptech.glide.util;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertEquals;\n\nimport android.graphics.Bitmap;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = 27)\npublic class UtilTest {\n\n  @Test\n  public void testReturnsCorrectBitmapSizeForDifferentDimensions() {\n    int width = 100;\n    int height = 100;\n    Bitmap.Config config = Bitmap.Config.ARGB_8888;\n\n    int initialSize = Util.getBitmapByteSize(width, height, config);\n    int sizeOne = Util.getBitmapByteSize(width * 2, height, config);\n    int sizeTwo = Util.getBitmapByteSize(width, height * 2, config);\n\n    assertEquals(4 * width * height, initialSize);\n    assertEquals(2 * initialSize, sizeOne);\n    assertEquals(2 * initialSize, sizeTwo);\n  }\n\n  @Test\n  public void testReturnsCorrectBitmapSizeForAlpha8Bitmap() {\n    int width = 110;\n    int height = 43;\n\n    int size = Util.getBitmapByteSize(width, height, Bitmap.Config.ALPHA_8);\n    assertEquals(width * height, size);\n  }\n\n  @Test\n  public void testReturnsCorrectBitmapSizeForRgb565() {\n    int width = 34;\n    int height = 1444;\n\n    int size = Util.getBitmapByteSize(width, height, Bitmap.Config.RGB_565);\n    assertEquals(width * height * 2, size);\n  }\n\n  @Test\n  public void testReturnsCorrectBitmapSizeForARGB4444() {\n    int width = 4454;\n    int height = 1235;\n\n    int size = Util.getBitmapByteSize(width, height, Bitmap.Config.ARGB_4444);\n    assertEquals(width * height * 2, size);\n  }\n\n  @Test\n  public void testReturnsCorrectBitmapSizeForARGB8888() {\n    int width = 943;\n    int height = 3584;\n\n    int size = Util.getBitmapByteSize(width, height, Bitmap.Config.ARGB_8888);\n    assertEquals(width * height * 4, size);\n  }\n\n  @Test\n  public void testReturnsLargestSizeForNullConfig() {\n    int width = 999;\n    int height = 41324;\n    int size = Util.getBitmapByteSize(width, height, null);\n    assertEquals(width * height * 4, size);\n  }\n\n  @Test\n  public void getBitmapByteSize_withRGBA_F16_returnsCorrectSize() {\n    int width = 100;\n    int height = 200;\n    assertThat(Util.getBitmapByteSize(width, height, Bitmap.Config.RGBA_F16))\n        .isEqualTo(width * height * 8);\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/com/bumptech/glide/util/ViewPreloadSizeProviderTest.java",
    "content": "package com.bumptech.glide.util;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertNull;\n\nimport android.view.View;\nimport android.view.ViewGroup;\nimport androidx.test.core.app.ApplicationProvider;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK)\npublic class ViewPreloadSizeProviderTest {\n\n  private View view;\n  private ViewPreloadSizeProvider<Object> provider;\n\n  @Before\n  public void setUp() {\n    view = new View(ApplicationProvider.getApplicationContext());\n    provider = new ViewPreloadSizeProvider<>();\n  }\n\n  @Test\n  public void testReturnsNullFromGetPreloadSizeBeforeHasSize() {\n    assertNull(provider.getPreloadSize(new Object(), 0, 0));\n  }\n\n  @Test\n  public void testReturnsValidSizeFromGetPreloadSizeAfterHasSize() {\n    int width = 4123;\n    int height = 342;\n    provider.onSizeReady(width, height);\n\n    int[] size = provider.getPreloadSize(new Object(), 0, 0);\n    assertThat(size).asList().containsExactly(width, height).inOrder();\n  }\n\n  @Test\n  public void testDoesNotObtainSizeFromViewOnceSizeIsSet() {\n    int width = 123;\n    int height = 456;\n    provider.onSizeReady(width, height);\n    view.setLayoutParams(new ViewGroup.LayoutParams(1, 1));\n    view.layout(0, 0, 1, 1);\n\n    provider.setView(view);\n\n    int[] size = provider.getPreloadSize(new Object(), 0, 0);\n    assertThat(size).asList().containsExactly(width, height).inOrder();\n  }\n\n  @Test\n  public void testCanObtainFixedSizeFromView() {\n    int width = 123;\n    int height = 456;\n    view.setLayoutParams(new ViewGroup.LayoutParams(width, height));\n    view.layout(0, 0, width, height);\n\n    provider.setView(view);\n\n    int[] size = provider.getPreloadSize(new Object(), 0, 0);\n    assertThat(size).asList().containsExactly(width, height).inOrder();\n  }\n\n  @Test\n  public void testIgnoresNewViewIfAlreadyWaitingOnSizeOfAnotherView() {\n    provider.setView(view);\n\n    View newView = new View(ApplicationProvider.getApplicationContext());\n    newView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));\n    provider.setView(newView);\n\n    assertNull(provider.getPreloadSize(new Object(), 0, 0));\n  }\n\n  @Test\n  public void testCanObtainSizeFromViewWhenGivenViewInConstructor() {\n    int width = 100;\n    int height = 200;\n    view.setLayoutParams(new ViewGroup.LayoutParams(width, height));\n    view.layout(0, 0, width, height);\n\n    provider = new ViewPreloadSizeProvider<>(view);\n\n    int[] size = provider.getPreloadSize(new Object(), 0, 0);\n    assertThat(size).asList().containsExactly(width, height).inOrder();\n  }\n}\n"
  },
  {
    "path": "library/test/src/test/java/opengles/GL.java",
    "content": "package javax.microedition.khronos.opengles;\n\n/**\n * TODO: Figure out why this is necessary and remove it. See:\n * https://github.com/robolectric/robolectric-gradle-plugin/issues/145\n */\npublic interface GL {}\n"
  },
  {
    "path": "library/test/src/test/resources/org.robolectric.Config.properties",
    "content": "# Exists only to \"enable\" logging for test coverage.\n# TODO: when we can ignore Log.* via configuration, remove this line.\nshadows=com.bumptech.glide.tests.GlideShadowLog\n"
  },
  {
    "path": "mocks/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.mocks\"\n\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    implementation(libs.androidx.annotation)\n    implementation(libs.guava)\n    implementation(libs.mockito.core)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "mocks/gradle.properties",
    "content": "POM_NAME=Glide mocks\nPOM_ARTIFACT_ID=mocks\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=A set of mocks to ease testing with Glide\n\nJAR_PREFIX=glide-\nJAR_POSTFIX=\n"
  },
  {
    "path": "mocks/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"InvalidPackage\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "mocks/src/main/java/com/bumptech/glide/load/engine/executor/MockGlideExecutor.java",
    "content": "package com.bumptech.glide.load.engine.executor;\n\nimport android.os.StrictMode;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.VisibleForTesting;\nimport com.google.common.util.concurrent.ForwardingExecutorService;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\n\n/** Creates mock {@link GlideExecutor}s. */\n@VisibleForTesting\npublic final class MockGlideExecutor {\n  private MockGlideExecutor() {\n    // Utility class.\n  }\n\n  // Public API.\n  @SuppressWarnings(\"WeakerAccess\")\n  public static GlideExecutor newTestExecutor(ExecutorService executorService) {\n    return new GlideExecutor(executorService);\n  }\n\n  public static GlideExecutor newMainThreadExecutor() {\n    return newTestExecutor(new DirectExecutorService());\n  }\n\n  /**\n   * @deprecated Use {@link #newMainThreadExecutor} instead.\n   */\n  @Deprecated\n  public static GlideExecutor newMainThreadUnlimitedExecutor() {\n    return newMainThreadExecutor();\n  }\n\n  /**\n   * DirectExecutorService that enforces StrictMode and converts ExecutionExceptions into\n   * RuntimeExceptions.\n   */\n  private static final class DirectExecutorService extends ForwardingExecutorService {\n    private static final StrictMode.ThreadPolicy THREAD_POLICY =\n        new StrictMode.ThreadPolicy.Builder().detectNetwork().penaltyDeath().build();\n\n    private final ExecutorService delegate;\n\n    DirectExecutorService() {\n      delegate = MoreExecutors.newDirectExecutorService();\n    }\n\n    @Override\n    protected ExecutorService delegate() {\n      return delegate;\n    }\n\n    @NonNull\n    @Override\n    public <T> Future<T> submit(@NonNull Runnable task, @NonNull T result) {\n      return getUninterruptibly(super.submit(task, result));\n    }\n\n    @NonNull\n    @Override\n    public <T> Future<T> submit(@NonNull Callable<T> task) {\n      return getUninterruptibly(super.submit(task));\n    }\n\n    @NonNull\n    @Override\n    public Future<?> submit(@NonNull Runnable task) {\n      return getUninterruptibly(super.submit(task));\n    }\n\n    @Override\n    public void execute(@NonNull final Runnable command) {\n      delegate.execute(\n          new Runnable() {\n            @Override\n            public void run() {\n              StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();\n              StrictMode.setThreadPolicy(THREAD_POLICY);\n              try {\n                command.run();\n              } finally {\n                StrictMode.setThreadPolicy(oldPolicy);\n              }\n            }\n          });\n    }\n\n    private <T> Future<T> getUninterruptibly(Future<T> future) {\n      boolean interrupted = false;\n      try {\n        while (!future.isDone()) {\n          try {\n            future.get();\n          } catch (ExecutionException e) {\n            throw new RuntimeException(e);\n          } catch (InterruptedException e) {\n            interrupted = true;\n          }\n        }\n      } finally {\n        if (interrupted) {\n          Thread.currentThread().interrupt();\n        }\n      }\n      return future;\n    }\n  }\n}\n"
  },
  {
    "path": "mocks/src/main/java/com/bumptech/glide/mocks/AnswerSelf.java",
    "content": "package com.bumptech.glide.mocks;\n\nimport static org.mockito.Mockito.RETURNS_DEFAULTS;\n\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\n/**\n * Useful default when mocking {@link com.bumptech.glide.request.RequestOptions} or {@link\n * com.bumptech.glide.RequestBuilder}.\n *\n * @param <T> The type of the options and/or builder.\n */\nfinal class AnswerSelf<T> implements Answer<T> {\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public T answer(InvocationOnMock invocation) throws Throwable {\n    Object mock = invocation.getMock();\n    if (invocation.getMethod().getReturnType().isInstance(mock)) {\n      return (T) mock;\n    } else {\n      return (T) RETURNS_DEFAULTS.answer(invocation);\n    }\n  }\n}\n"
  },
  {
    "path": "mocks/src/main/java/com/bumptech/glide/mocks/MockGlideBuilders.java",
    "content": "package com.bumptech.glide.mocks;\n\nimport static org.mockito.Mockito.mock;\n\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.request.RequestOptions;\n\n/**\n * Mocks for various builder patterns in Glide to make testing easier.\n *\n * <p>All methods share the same behavior. Any method on the builder that returns the builder itself\n * will default to returning the mock rather than null. Any method on the builder that returns\n * anything other than the builder will return Mockito's standard default return value.\n */\npublic final class MockGlideBuilders {\n\n  private MockGlideBuilders() {}\n\n  /** Creates a new {@link RequestBuilder} instance with a matching resource type. */\n  @SuppressWarnings(\"unchecked\")\n  public static <T> RequestBuilder<T> mockRequestBuilder() {\n    return (RequestBuilder<T>) mockGlideRequest(RequestBuilder.class);\n  }\n\n  /** Creates a new instance of a generated {@code GlideRequest} class for an application. */\n  // The suppressions allow callers to get a typed class without warnings in their test code.\n  @SuppressWarnings({\"unchecked\", \"TypeParameterUnusedInFormals\"})\n  public static <T, Y extends RequestBuilder<T>> Y mockGlideRequest(Class<?> glideRequest) {\n    return (Y) mock(glideRequest, new AnswerSelf<Y>());\n  }\n\n  /** Creates a new {@link RequestOptions} instance. */\n  public static RequestOptions mockRequestOptions() {\n    return mockGlideOptions(RequestOptions.class);\n  }\n\n  /** Creates a new instance of a generated {@code GlideOptions} class for an application. */\n  public static <T extends RequestOptions> T mockGlideOptions(Class<T> glideOptionsClass) {\n    return mock(glideOptionsClass, new AnswerSelf<T>());\n  }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:recommended\"\n  ],\n  \"semanticCommits\": \"disabled\",\n  \"packageRules\": [\n    {\n      \"matchUpdateTypes\": [\"minor\", \"patch\", \"pin\", \"digest\"],\n      \"automerge\": true,\n      \"automergeType\": \"branch\"\n    }\n  ]\n}\n"
  },
  {
    "path": "samples/contacturi/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.samples.contacturi\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n        \n\n        versionCode = 1\n        versionName = \"1.0\"\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    implementation(libs.androidx.appcompat)\n    annotationProcessor(project(\":annotation:compiler\"))\n}"
  },
  {
    "path": "samples/contacturi/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"GoogleAppIndexingWarning\" severity=\"ignore\"/>\n    <issue id=\"GradleDependency\" severity=\"ignore\"/>\n    <issue id=\"Autofill\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "samples/contacturi/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.READ_CONTACTS\" />\n\n    <application\n        android:allowBackup=\"false\"\n        android:icon=\"@android:drawable/sym_def_app_icon\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/Theme.AppCompat\" >\n        <activity\n            android:name=\".MainActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "samples/contacturi/src/main/java/com/bumptech/glide/samples/contacturi/ContactUriModule.java",
    "content": "package com.bumptech.glide.samples.contacturi;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\n\n/** Ensures that Glide's generated API is created for the Contact Uri sample. */\n@GlideModule\npublic class ContactUriModule extends AppGlideModule {\n  // Intentionally empty.\n}\n"
  },
  {
    "path": "samples/contacturi/src/main/java/com/bumptech/glide/samples/contacturi/MainActivity.java",
    "content": "package com.bumptech.glide.samples.contacturi;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.ContentUris;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.provider.ContactsContract;\nimport android.provider.ContactsContract.Contacts;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.ImageView;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.util.Preconditions;\n\n/**\n * An activity that demonstrates loading photos using {@link\n * com.bumptech.glide.load.data.StreamLocalUriFetcher content uris} through Glide. It works by\n * making the user to choose a contact when presses a button, and after he chooses a contact with\n * photo, We try to load both a high res image and thumbnail image of that contact with various\n * Uris.\n */\npublic class MainActivity extends Activity {\n  private static final int REQUEST_CONTACT = 1;\n  private static final int READ_CONTACTS = 0;\n\n  private ImageView imageViewContact;\n  private ImageView imageViewLookup;\n  private ImageView imageViewPhoto;\n  private ImageView imageViewDisplayPhoto;\n  private EditText numberEntry;\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n\n    imageViewContact = findViewById(R.id.image_contact);\n    imageViewLookup = findViewById(R.id.image_lookup);\n    imageViewPhoto = findViewById(R.id.image_photo);\n    imageViewDisplayPhoto = findViewById(R.id.image_display_photo);\n    numberEntry = findViewById(R.id.number_entry);\n    // Make sure that user gives application required permissions\n    if (ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.READ_CONTACTS)\n        != PackageManager.PERMISSION_GRANTED) {\n      // No explanation needed, we can request the permission.\n      ActivityCompat.requestPermissions(\n          this, new String[] {Manifest.permission.READ_CONTACTS}, READ_CONTACTS);\n    }\n\n    findViewById(R.id.button_pick_contact)\n        .setOnClickListener(\n            new View.OnClickListener() {\n              @Override\n              public void onClick(View v) {\n                Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);\n                startActivityForResult(intent, REQUEST_CONTACT);\n              }\n            });\n\n    findViewById(R.id.button_find)\n        .setOnClickListener(\n            new View.OnClickListener() {\n              @Override\n              public void onClick(View v) {\n                Uri uri =\n                    Uri.withAppendedPath(\n                        ContactsContract.PhoneLookup.CONTENT_FILTER_URI,\n                        Uri.encode(numberEntry.getText().toString()));\n                GlideApp.with(MainActivity.this)\n                    .load(uri)\n                    .override(Target.SIZE_ORIGINAL)\n                    .into(imageViewLookup);\n              }\n            });\n  }\n\n  @Override\n  protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n    if (requestCode == REQUEST_CONTACT && resultCode == RESULT_OK) {\n      Uri uri = Preconditions.checkNotNull(data.getData());\n      final Cursor cursor = getContentResolver().query(uri, null, null, null, null);\n      try {\n        if (cursor != null && cursor.moveToFirst()) {\n          final long contactId = cursor.getLong(cursor.getColumnIndex(Contacts._ID));\n          showContact(contactId);\n        }\n      } finally {\n        if (cursor != null) {\n          cursor.close();\n        }\n      }\n      return;\n    }\n    super.onActivityResult(requestCode, resultCode, data);\n  }\n\n  private void showContact(long id) {\n    GlideRequests glideRequests = GlideApp.with(this);\n    RequestOptions originalSize = new RequestOptions().override(Target.SIZE_ORIGINAL);\n\n    Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id);\n    glideRequests.load(contactUri).apply(originalSize).into(imageViewContact);\n\n    Uri lookupUri = Contacts.getLookupUri(getContentResolver(), contactUri);\n    glideRequests.load(lookupUri).apply(originalSize).into(imageViewLookup);\n\n    Uri photoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);\n    glideRequests.load(photoUri).apply(originalSize).into(imageViewPhoto);\n\n    Uri displayPhotoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.DISPLAY_PHOTO);\n    glideRequests.load(displayPhotoUri).apply(originalSize).into(imageViewDisplayPhoto);\n  }\n}\n"
  },
  {
    "path": "samples/contacturi/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\"\n        android:padding=\"@dimen/activity_horizontal_margin\"\n        tools:context=\"com.bumptech.glide.samples.contacturi.MainActivity\">\n    <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n        <Button\n                android:id=\"@+id/button_pick_contact\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/pick_contact\"/>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"horizontal\">\n\n            <EditText\n                android:id=\"@+id/number_entry\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:ems=\"10\"\n                android:hint=\"@string/hint_number_entry\"\n                android:inputType=\"phone\" />\n\n            <Button\n                android:id=\"@+id/button_find\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/button_search_phone\" />\n        </LinearLayout>\n\n        <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/activity_vertical_margin\"\n                android:text=\"@string/image_contact\"/>\n        <ImageView\n                android:id=\"@+id/image_contact\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:contentDescription=\"@string/image_contact\"\n                tools:src=\"@android:drawable/sym_def_app_icon\"/>\n\n        <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/activity_vertical_margin\"\n                android:text=\"@string/image_lookup\"/>\n        <ImageView\n                android:id=\"@+id/image_lookup\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:contentDescription=\"@string/image_lookup\"\n                tools:src=\"@android:drawable/sym_def_app_icon\"/>\n\n        <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/activity_vertical_margin\"\n                android:text=\"@string/image_photo\"/>\n        <ImageView\n                android:id=\"@+id/image_photo\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:contentDescription=\"@string/image_photo\"\n                tools:src=\"@android:drawable/sym_def_app_icon\"/>\n\n        <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/activity_vertical_margin\"\n                android:text=\"@string/image_display_photo\"/>\n        <ImageView\n                android:id=\"@+id/image_display_photo\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:contentDescription=\"@string/image_display_photo\"\n                tools:src=\"@android:drawable/sym_def_app_icon\"/>\n    </LinearLayout>\n</ScrollView>\n"
  },
  {
    "path": "samples/contacturi/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "samples/contacturi/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">ContactUri Sample</string>\n    <string name=\"pick_contact\">Pick Contact</string>\n    <string name=\"image_contact\">Image based on contact Uri, best size on latest Android</string>\n    <string name=\"image_lookup\">Image based on lookup Uri, same as contact Uri</string>\n    <string name=\"image_photo\">Image based on photo Uri, thumbnail sized, usually 96x96px</string>\n    <string name=\"image_display_photo\">Image based on display photo Uri, possibly 512x512px</string>\n    <string name=\"hint_number_entry\">Find by phone number</string>\n    <string name=\"button_search_phone\">Find</string>\n</resources>\n"
  },
  {
    "path": "samples/flickr/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.samples.flickr\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n        \n\n        versionCode = 1\n        versionName = \"1.0\"\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n\n    implementation(project(\":integration:recyclerview\")) {\n        isTransitive = false\n    }\n\n    annotationProcessor(project(\":annotation:compiler\"))\n\n    implementation(libs.androidx.appcompat)\n    implementation(libs.volley)\n    implementation(libs.androidx.recyclerview)\n}"
  },
  {
    "path": "samples/flickr/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <!-- Not supported by all build systems -->\n    <issue id=\"GradleOverrides\" severity=\"ignore\" />\n    <issue id=\"IconMissingDensityFolder\" severity=\"ignore\"/>\n    <issue id=\"GoogleAppIndexingWarning\" severity=\"ignore\"/>\n    <issue id=\"GradleDependency\" severity=\"ignore\" />\n    <issue id=\"Autofill\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "samples/flickr/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n    <!--\n    Allows Glide to monitor connectivity status and restart failed requests if users go from a\n    a disconnected to a connected network state.\n    -->\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>\n\n    <application\n        android:label=\"@string/app_name\"\n        android:icon=\"@android:drawable/sym_def_app_icon\"\n        android:allowBackup=\"false\"\n        android:networkSecurityConfig=\"@xml/network_security_config\"\n        android:theme=\"@style/Theme.AppCompat\"\n        tools:targetApi=\"n\">\n\n        <activity\n            android:name=\".FlickrSearchActivity\"\n            android:exported=\"true\"\n            android:launchMode=\"singleTask\"\n            android:windowSoftInputMode=\"stateHidden|adjustResize\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:exported=\"false\"\n            android:name=\".FullscreenActivity\"/>\n    </application>\n</manifest>\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrGlideExtension.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.annotation.GlideExtension;\nimport com.bumptech.glide.annotation.GlideOption;\nimport com.bumptech.glide.request.BaseRequestOptions;\nimport com.bumptech.glide.samples.flickr.api.Api;\n\n/** Extension methods for the Flickr sample's generated API. */\n// Required by Glide's annotation processor.\n@SuppressWarnings({\"WeakerAccess\", \"unused\"})\n@GlideExtension\npublic final class FlickrGlideExtension {\n\n  private FlickrGlideExtension() {\n    // Utility class.\n  }\n\n  @NonNull\n  @GlideOption\n  public static BaseRequestOptions<?> squareThumb(BaseRequestOptions<?> requestOptions) {\n    return requestOptions.centerCrop();\n  }\n\n  @NonNull\n  @GlideOption\n  public static BaseRequestOptions<?> squareMiniThumb(BaseRequestOptions<?> requestOptions) {\n    return requestOptions.centerCrop().override(Api.SQUARE_THUMB_SIZE);\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrGlideModule.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.GlideBuilder;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.load.DecodeFormat;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.bumptech.glide.request.RequestOptions;\nimport com.bumptech.glide.samples.flickr.api.Photo;\nimport java.io.InputStream;\n\n/** Register {@link FlickrModelLoader} for the Flickr sample app. */\n@GlideModule\npublic class FlickrGlideModule extends AppGlideModule {\n\n  @Override\n  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n    super.applyOptions(context, builder);\n    builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_ARGB_8888));\n  }\n\n  @Override\n  public void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    registry.append(Photo.class, InputStream.class, new FlickrModelLoader.Factory());\n  }\n\n  // Disable manifest parsing to avoid adding similar modules twice.\n  @Override\n  public boolean isManifestParsingEnabled() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrModelLoader.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelCache;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.load.model.stream.BaseGlideUrlLoader;\nimport com.bumptech.glide.samples.flickr.api.Api;\nimport com.bumptech.glide.samples.flickr.api.Photo;\nimport java.io.InputStream;\nimport java.util.List;\n\n/**\n * An implementation of ModelStreamLoader that leverages the StreamOpener class and the\n * ExecutorService backing the Engine to download the image and resize it in memory before saving\n * the resized version directly to the disk cache.\n */\npublic final class FlickrModelLoader extends BaseGlideUrlLoader<Photo> {\n\n  /** The default factory for {@link com.bumptech.glide.samples.flickr.FlickrModelLoader}s. */\n  public static class Factory implements ModelLoaderFactory<Photo, InputStream> {\n    private final ModelCache<Photo, GlideUrl> modelCache = new ModelCache<>(500);\n\n    @NonNull\n    @Override\n    public ModelLoader<Photo, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new FlickrModelLoader(\n          multiFactory.build(GlideUrl.class, InputStream.class), modelCache);\n    }\n\n    @Override\n    public void teardown() {}\n  }\n\n  private FlickrModelLoader(\n      ModelLoader<GlideUrl, InputStream> urlLoader, ModelCache<Photo, GlideUrl> modelCache) {\n    super(urlLoader, modelCache);\n  }\n\n  @Override\n  public boolean handles(@NonNull Photo model) {\n    return true;\n  }\n\n  @Override\n  protected String getUrl(Photo model, int width, int height, Options options) {\n    return Api.getPhotoURL(model, width, height);\n  }\n\n  @Override\n  protected List<String> getAlternateUrls(Photo photo, int width, int height, Options options) {\n    return Api.getAlternateUrls(photo, width, height);\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport android.content.Intent;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.ListPreloader;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader;\nimport com.bumptech.glide.samples.flickr.api.Api;\nimport com.bumptech.glide.samples.flickr.api.Photo;\nimport com.bumptech.glide.util.FixedPreloadSizeProvider;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * A fragment that shows square image thumbnails whose size is determined by the fragment's\n * arguments in a grid pattern.\n */\npublic class FlickrPhotoGrid extends Fragment implements PhotoViewer {\n  private static final String STATE_POSITION_INDEX = \"state_position_index\";\n\n  private static final String IMAGE_SIZE_KEY = \"image_size\";\n  private static final String PRELOAD_KEY = \"preload\";\n  private static final String THUMBNAIL_KEY = \"thumbnail\";\n\n  private PhotoAdapter adapter;\n  private List<Photo> currentPhotos;\n  private int photoSize;\n  private RecyclerView grid;\n  private boolean thumbnail;\n  private GlideRequest<Drawable> fullRequest;\n  private GlideRequest<Drawable> thumbnailRequest;\n  private GlideRequest<Drawable> preloadRequest;\n  private GridLayoutManager layoutManager;\n\n  public static FlickrPhotoGrid newInstance(int size, int preloadCount, boolean thumbnail) {\n    FlickrPhotoGrid photoGrid = new FlickrPhotoGrid();\n    Bundle args = new Bundle();\n    args.putInt(IMAGE_SIZE_KEY, size);\n    args.putInt(PRELOAD_KEY, preloadCount);\n    args.putBoolean(THUMBNAIL_KEY, thumbnail);\n    photoGrid.setArguments(args);\n    return photoGrid;\n  }\n\n  @Override\n  public View onCreateView(\n      @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n    Bundle args = Preconditions.checkNotNull(getArguments());\n    photoSize = args.getInt(IMAGE_SIZE_KEY);\n    thumbnail = args.getBoolean(THUMBNAIL_KEY);\n\n    fullRequest = GlideApp.with(this).asDrawable().centerCrop();\n\n    thumbnailRequest =\n        GlideApp.with(this).asDrawable().centerCrop().override(Api.SQUARE_THUMB_SIZE);\n\n    preloadRequest = thumbnail ? thumbnailRequest.clone().priority(Priority.HIGH) : fullRequest;\n\n    final View result = inflater.inflate(R.layout.flickr_photo_grid, container, false);\n\n    final int gridMargin = getResources().getDimensionPixelOffset(R.dimen.grid_margin);\n    int spanCount = getResources().getDisplayMetrics().widthPixels / (photoSize + (2 * gridMargin));\n    grid = result.findViewById(R.id.flickr_photo_grid);\n    layoutManager = new GridLayoutManager(getActivity(), spanCount);\n    grid.setLayoutManager(layoutManager);\n\n    grid.addItemDecoration(\n        new RecyclerView.ItemDecoration() {\n          @Override\n          public void getItemOffsets(\n              Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {\n            outRect.set(gridMargin, gridMargin, gridMargin, gridMargin);\n          }\n        });\n    grid.addRecyclerListener(\n        new RecyclerView.RecyclerListener() {\n          @Override\n          public void onViewRecycled(RecyclerView.ViewHolder holder) {\n            PhotoViewHolder photoViewHolder = (PhotoViewHolder) holder;\n            GlideApp.with(FlickrPhotoGrid.this).clear(photoViewHolder.imageView);\n          }\n        });\n\n    int heightCount = getResources().getDisplayMetrics().heightPixels / photoSize;\n    grid.getRecycledViewPool().setMaxRecycledViews(0, spanCount * heightCount * 2);\n    grid.setItemViewCacheSize(0);\n    adapter = new PhotoAdapter();\n    grid.setAdapter(adapter);\n\n    FixedPreloadSizeProvider<Photo> preloadSizeProvider =\n        new FixedPreloadSizeProvider<>(photoSize, photoSize);\n    RecyclerViewPreloader<Photo> preloader =\n        new RecyclerViewPreloader<>(\n            Glide.with(this), adapter, preloadSizeProvider, args.getInt(PRELOAD_KEY));\n    grid.addOnScrollListener(preloader);\n\n    if (currentPhotos != null) {\n      adapter.setPhotos(currentPhotos);\n    }\n\n    if (savedInstanceState != null) {\n      int index = savedInstanceState.getInt(STATE_POSITION_INDEX);\n      grid.scrollToPosition(index);\n    }\n\n    return result;\n  }\n\n  @Override\n  public void onSaveInstanceState(@NonNull Bundle outState) {\n    super.onSaveInstanceState(outState);\n    if (grid != null) {\n      int index = layoutManager.findFirstVisibleItemPosition();\n      outState.putInt(STATE_POSITION_INDEX, index);\n    }\n  }\n\n  @Override\n  public void onPhotosUpdated(List<Photo> photos) {\n    currentPhotos = photos;\n    if (adapter != null) {\n      adapter.setPhotos(currentPhotos);\n    }\n  }\n\n  private class PhotoAdapter extends RecyclerView.Adapter<PhotoViewHolder>\n      implements ListPreloader.PreloadModelProvider<Photo> {\n    private final LayoutInflater inflater;\n    private List<Photo> photos = Collections.emptyList();\n\n    PhotoAdapter() {\n      this.inflater = LayoutInflater.from(getActivity());\n    }\n\n    void setPhotos(List<Photo> photos) {\n      this.photos = photos;\n      notifyDataSetChanged();\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n      return 0;\n    }\n\n    @Override\n    public PhotoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n      View view = inflater.inflate(R.layout.flickr_photo_grid_item, parent, false);\n      ViewGroup.LayoutParams params = view.getLayoutParams();\n      params.width = photoSize;\n      params.height = photoSize;\n      return new PhotoViewHolder(view);\n    }\n\n    @Override\n    public void onBindViewHolder(PhotoViewHolder holder, int position) {\n      final Photo current = photos.get(position);\n\n      fullRequest\n          .load(current)\n          .thumbnail(thumbnail ? thumbnailRequest.load(current) : null)\n          .into(holder.imageView);\n\n      holder.imageView.setOnClickListener(\n          new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n              Intent intent = FullscreenActivity.getIntent(getActivity(), current);\n              startActivity(intent);\n            }\n          });\n    }\n\n    @Override\n    public long getItemId(int i) {\n      return RecyclerView.NO_ID;\n    }\n\n    @Override\n    public int getItemCount() {\n      return photos.size();\n    }\n\n    @NonNull\n    @Override\n    public List<Photo> getPreloadItems(int position) {\n      return photos.subList(position, position + 1);\n    }\n\n    @Nullable\n    @Override\n    public RequestBuilder<Drawable> getPreloadRequestBuilder(@NonNull Photo item) {\n      return preloadRequest.load(item);\n    }\n  }\n\n  private static final class PhotoViewHolder extends RecyclerView.ViewHolder {\n    private final ImageView imageView;\n\n    PhotoViewHolder(View itemView) {\n      super(itemView);\n      imageView = (ImageView) itemView;\n    }\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoList.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;\n\nimport android.content.Intent;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport com.bumptech.glide.ListPreloader;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader;\nimport com.bumptech.glide.load.engine.DiskCacheStrategy;\nimport com.bumptech.glide.samples.flickr.api.Api;\nimport com.bumptech.glide.samples.flickr.api.Photo;\nimport com.bumptech.glide.util.ViewPreloadSizeProvider;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * A fragment that shows cropped image thumbnails half the width of the screen in a scrolling list.\n */\npublic class FlickrPhotoList extends Fragment implements PhotoViewer {\n  private static final int PRELOAD_AHEAD_ITEMS = 5;\n  private static final String STATE_POSITION_INDEX = \"state_position_index\";\n  private static final String STATE_POSITION_OFFSET = \"state_position_offset\";\n  private FlickrPhotoListAdapter adapter;\n  private List<Photo> currentPhotos;\n  private RecyclerView list;\n  private GlideRequest<Drawable> fullRequest;\n  private GlideRequest<Drawable> thumbRequest;\n  private ViewPreloadSizeProvider<Photo> preloadSizeProvider;\n  private LinearLayoutManager layoutManager;\n\n  public static FlickrPhotoList newInstance() {\n    return new FlickrPhotoList();\n  }\n\n  @Override\n  public void onPhotosUpdated(List<Photo> photos) {\n    currentPhotos = photos;\n    if (adapter != null) {\n      adapter.setPhotos(currentPhotos);\n    }\n  }\n\n  @Override\n  public View onCreateView(\n      @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n    final View result = inflater.inflate(R.layout.flickr_photo_list, container, false);\n\n    list = result.findViewById(R.id.flickr_photo_list);\n    layoutManager = new LinearLayoutManager(getActivity());\n    list.setLayoutManager(layoutManager);\n    adapter = new FlickrPhotoListAdapter();\n    list.setAdapter(adapter);\n\n    preloadSizeProvider = new ViewPreloadSizeProvider<>();\n    RecyclerViewPreloader<Photo> preloader =\n        new RecyclerViewPreloader<>(\n            GlideApp.with(this), adapter, preloadSizeProvider, PRELOAD_AHEAD_ITEMS);\n    list.addOnScrollListener(preloader);\n    list.setItemViewCacheSize(0);\n\n    if (currentPhotos != null) {\n      adapter.setPhotos(currentPhotos);\n    }\n\n    final GlideRequests glideRequests = GlideApp.with(this);\n    fullRequest =\n        glideRequests.asDrawable().centerCrop().placeholder(new ColorDrawable(Color.GRAY));\n\n    thumbRequest =\n        glideRequests\n            .asDrawable()\n            .diskCacheStrategy(DiskCacheStrategy.DATA)\n            .override(Api.SQUARE_THUMB_SIZE)\n            .transition(withCrossFade());\n\n    list.addRecyclerListener(\n        new RecyclerView.RecyclerListener() {\n          @Override\n          public void onViewRecycled(RecyclerView.ViewHolder holder) {\n            PhotoTitleViewHolder vh = (PhotoTitleViewHolder) holder;\n            glideRequests.clear(vh.imageView);\n          }\n        });\n\n    if (savedInstanceState != null) {\n      int index = savedInstanceState.getInt(STATE_POSITION_INDEX);\n      int offset = savedInstanceState.getInt(STATE_POSITION_OFFSET);\n      layoutManager.scrollToPositionWithOffset(index, offset);\n    }\n\n    return result;\n  }\n\n  @Override\n  public void onSaveInstanceState(@NonNull Bundle outState) {\n    super.onSaveInstanceState(outState);\n    if (list != null) {\n      int index = layoutManager.findFirstVisibleItemPosition();\n      View topView = list.getChildAt(0);\n      int offset = topView != null ? topView.getTop() : 0;\n      outState.putInt(STATE_POSITION_INDEX, index);\n      outState.putInt(STATE_POSITION_OFFSET, offset);\n    }\n  }\n\n  private final class FlickrPhotoListAdapter extends RecyclerView.Adapter<PhotoTitleViewHolder>\n      implements ListPreloader.PreloadModelProvider<Photo> {\n    private final LayoutInflater inflater;\n    private List<Photo> photos = Collections.emptyList();\n\n    FlickrPhotoListAdapter() {\n      this.inflater = LayoutInflater.from(getActivity());\n    }\n\n    void setPhotos(List<Photo> photos) {\n      this.photos = photos;\n      notifyDataSetChanged();\n    }\n\n    @Override\n    public PhotoTitleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n      View view = inflater.inflate(R.layout.flickr_photo_list_item, parent, false);\n      PhotoTitleViewHolder vh = new PhotoTitleViewHolder(view);\n      preloadSizeProvider.setView(vh.imageView);\n      return vh;\n    }\n\n    @Override\n    public void onBindViewHolder(PhotoTitleViewHolder holder, int position) {\n      final Photo current = photos.get(position);\n      fullRequest.load(current).thumbnail(thumbRequest.load(current)).into(holder.imageView);\n\n      holder.imageView.setOnClickListener(\n          new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n              Intent intent = FullscreenActivity.getIntent(getActivity(), current);\n              startActivity(intent);\n            }\n          });\n\n      holder.titleView.setText(current.getTitle());\n    }\n\n    @Override\n    public long getItemId(int i) {\n      return RecyclerView.NO_ID;\n    }\n\n    @Override\n    public int getItemCount() {\n      return photos.size();\n    }\n\n    @NonNull\n    @Override\n    public List<Photo> getPreloadItems(int position) {\n      return photos.subList(position, position + 1);\n    }\n\n    @Nullable\n    @Override\n    public RequestBuilder<Drawable> getPreloadRequestBuilder(@NonNull Photo item) {\n      return fullRequest.thumbnail(thumbRequest.load(item)).load(item);\n    }\n  }\n\n  private static final class PhotoTitleViewHolder extends RecyclerView.ViewHolder {\n    private final TextView titleView;\n    private final ImageView imageView;\n\n    PhotoTitleViewHolder(View itemView) {\n      super(itemView);\n      imageView = itemView.findViewById(R.id.photo_view);\n      titleView = itemView.findViewById(R.id.title_view);\n    }\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Process;\nimport android.os.StrictMode;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.SearchView;\nimport android.widget.TextView;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentPagerAdapter;\nimport androidx.viewpager.widget.ViewPager;\nimport com.bumptech.glide.load.engine.prefill.PreFillType;\nimport com.bumptech.glide.request.FutureTarget;\nimport com.bumptech.glide.samples.flickr.api.Api;\nimport com.bumptech.glide.samples.flickr.api.Photo;\nimport com.bumptech.glide.samples.flickr.api.Query;\nimport com.bumptech.glide.samples.flickr.api.SearchQuery;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\n\n/**\n * An activity that allows users to search for images on Flickr and that contains a series of\n * fragments that display retrieved image thumbnails.\n */\npublic class FlickrSearchActivity extends AppCompatActivity\n    implements SearchView.OnQueryTextListener {\n  private static final String TAG = \"FlickrSearchActivity\";\n  private static final String STATE_QUERY = \"state_search_string\";\n  private static final Query DEFAULT_QUERY = new SearchQuery(\"airplane\").requireSafeOverQuality();\n\n  private final QueryListener queryListener = new QueryListener();\n  private final Set<PhotoViewer> photoViewers = new HashSet<>();\n\n  private List<Photo> currentPhotos = new ArrayList<>();\n  private View searching;\n  private TextView searchTerm;\n  private View searchLoading;\n  private BackgroundThumbnailFetcher backgroundThumbnailFetcher;\n  private HandlerThread backgroundThread;\n  private Handler backgroundHandler;\n  private SearchView searchView;\n  private Query currentQuery;\n\n  private enum Page {\n    SMALL,\n    MEDIUM,\n    LIST\n  }\n\n  private static final Map<Page, Integer> PAGE_TO_TITLE;\n\n  static {\n    Map<Page, Integer> temp = new HashMap<>();\n    temp.put(Page.SMALL, R.string.small);\n    temp.put(Page.MEDIUM, R.string.medium);\n    temp.put(Page.LIST, R.string.list);\n    PAGE_TO_TITLE = Collections.unmodifiableMap(temp);\n  }\n\n  @Override\n  public void onAttachFragment(Fragment fragment) {\n    super.onAttachFragment(fragment);\n    if (fragment instanceof PhotoViewer) {\n      PhotoViewer photoViewer = (PhotoViewer) fragment;\n      photoViewer.onPhotosUpdated(currentPhotos);\n      if (!photoViewers.contains(photoViewer)) {\n        photoViewers.add(photoViewer);\n      }\n    }\n  }\n\n  @Override\n  public boolean onCreateOptionsMenu(Menu menu) {\n    MenuInflater menuInflater = getMenuInflater();\n    menuInflater.inflate(R.menu.search_activity, menu);\n\n    searchView = (SearchView) menu.findItem(R.id.search).getActionView();\n    searchView.setSubmitButtonEnabled(true);\n    searchView.setIconified(false);\n    searchView.setOnQueryTextListener(this);\n\n    return true;\n  }\n\n  @Override\n  public boolean onQueryTextSubmit(String query) {\n    executeSearch(query);\n    searchView.setQuery(\"\", false /*submit*/);\n    return true;\n  }\n\n  @Override\n  public boolean onQueryTextChange(String newText) {\n    return false;\n  }\n\n  /** Called when the activity is first created. */\n  @Override\n  public void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    StrictMode.setThreadPolicy(\n        new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());\n\n    backgroundThread = new HandlerThread(\"BackgroundThumbnailHandlerThread\");\n    backgroundThread.start();\n    backgroundHandler = new Handler(backgroundThread.getLooper());\n\n    setContentView(R.layout.flickr_search_activity);\n    searching = findViewById(R.id.searching);\n    searchLoading = findViewById(R.id.search_loading);\n    searchTerm = (TextView) findViewById(R.id.search_term);\n\n    Resources res = getResources();\n    ViewPager pager = (ViewPager) findViewById(R.id.view_pager);\n    pager.setPageMargin(res.getDimensionPixelOffset(R.dimen.page_margin));\n    pager.setAdapter(new FlickrPagerAdapter(getSupportFragmentManager()));\n\n    Api.get(this).registerSearchListener(queryListener);\n    if (savedInstanceState != null) {\n      Query savedQuery = savedInstanceState.getParcelable(STATE_QUERY);\n      if (savedQuery != null) {\n        executeQuery(savedQuery);\n      }\n    } else {\n      executeQuery(DEFAULT_QUERY);\n    }\n\n    int smallGridSize = res.getDimensionPixelSize(R.dimen.small_photo_side);\n    int mediumGridSize = res.getDimensionPixelSize(R.dimen.medium_photo_side);\n    int listHeightSize = res.getDimensionPixelSize(R.dimen.flickr_list_item_height);\n    int screenWidth = getScreenWidth();\n\n    if (savedInstanceState == null) {\n      // Weight values determined experimentally by measuring the number of incurred GCs while\n      // scrolling through the various photo grids/lists.\n      GlideApp.get(this)\n          .preFillBitmapPool(\n              new PreFillType.Builder(smallGridSize).setWeight(1),\n              new PreFillType.Builder(mediumGridSize).setWeight(1),\n              new PreFillType.Builder(screenWidth / 2, listHeightSize).setWeight(6));\n    }\n  }\n\n  private int getScreenWidth() {\n    return getResources().getDisplayMetrics().widthPixels;\n  }\n\n  @Override\n  protected void onSaveInstanceState(@NonNull Bundle outState) {\n    super.onSaveInstanceState(outState);\n    if (currentQuery != null) {\n      outState.putParcelable(STATE_QUERY, currentQuery);\n    }\n  }\n\n  @Override\n  protected void onDestroy() {\n    super.onDestroy();\n    Api.get(this).unregisterSearchListener(queryListener);\n    if (backgroundThumbnailFetcher != null) {\n      backgroundThumbnailFetcher.cancel();\n      backgroundThumbnailFetcher = null;\n      backgroundThread.quit();\n      backgroundThread = null;\n    }\n  }\n\n  private void executeSearch(String searchString) {\n    Query query = TextUtils.isEmpty(searchString) ? null : new SearchQuery(searchString);\n    executeQuery(query);\n  }\n\n  private void executeQuery(Query query) {\n    currentQuery = query;\n    if (query == null) {\n      queryListener.onSearchCompleted(null, Collections.<Photo>emptyList());\n      return;\n    }\n\n    searching.setVisibility(View.VISIBLE);\n    searchLoading.setVisibility(View.VISIBLE);\n    searchTerm.setText(getString(R.string.searching_for, currentQuery.getDescription()));\n\n    Api.get(this).query(currentQuery);\n  }\n\n  private class QueryListener implements Api.QueryListener {\n    @Override\n    public void onSearchCompleted(Query query, List<Photo> photos) {\n      if (!isCurrentQuery(query)) {\n        return;\n      }\n\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Search completed, got \" + photos.size() + \" results\");\n      }\n      searching.setVisibility(View.INVISIBLE);\n\n      for (PhotoViewer viewer : photoViewers) {\n        viewer.onPhotosUpdated(photos);\n      }\n\n      if (backgroundThumbnailFetcher != null) {\n        backgroundThumbnailFetcher.cancel();\n      }\n\n      backgroundThumbnailFetcher =\n          new BackgroundThumbnailFetcher(FlickrSearchActivity.this, photos);\n      backgroundHandler.post(backgroundThumbnailFetcher);\n\n      currentPhotos = photos;\n    }\n\n    private boolean isCurrentQuery(Query query) {\n      return currentQuery != null && currentQuery.equals(query);\n    }\n\n    @Override\n    public void onSearchFailed(Query query, Exception e) {\n      if (!isCurrentQuery(query)) {\n        return;\n      }\n\n      if (Log.isLoggable(TAG, Log.ERROR)) {\n        Log.e(TAG, \"Search failed\", e);\n      }\n      searching.setVisibility(View.VISIBLE);\n      searchLoading.setVisibility(View.INVISIBLE);\n      searchTerm.setText(getString(R.string.search_failed, currentQuery.getDescription()));\n    }\n  }\n\n  private final class FlickrPagerAdapter extends FragmentPagerAdapter {\n\n    private int mLastPosition = -1;\n    private Fragment mLastFragment;\n\n    FlickrPagerAdapter(FragmentManager fm) {\n      super(fm);\n    }\n\n    @Override\n    public Fragment getItem(int position) {\n      return pageToFragment(position);\n    }\n\n    @Override\n    public void setPrimaryItem(ViewGroup container, int position, Object object) {\n      super.setPrimaryItem(container, position, object);\n      if (position != mLastPosition) {\n        if (mLastPosition >= 0) {\n          GlideApp.with(mLastFragment).pauseRequests();\n        }\n        Fragment current = (Fragment) object;\n        mLastPosition = position;\n        mLastFragment = current;\n        if (current.isAdded()) {\n          GlideApp.with(current).resumeRequests();\n        }\n      }\n    }\n\n    @Override\n    public int getCount() {\n      return Page.values().length;\n    }\n\n    @Override\n    public CharSequence getPageTitle(int position) {\n      Page page = Page.values()[position];\n      int titleId = PAGE_TO_TITLE.get(page);\n      return getString(titleId);\n    }\n\n    private Fragment pageToFragment(int position) {\n      Page page = Page.values()[position];\n      if (page == Page.SMALL) {\n        int pageSize = getPageSize(R.dimen.small_photo_side);\n        return FlickrPhotoGrid.newInstance(pageSize, 15, false /*thumbnail*/);\n      } else if (page == Page.MEDIUM) {\n        int pageSize = getPageSize(R.dimen.medium_photo_side);\n        return FlickrPhotoGrid.newInstance(pageSize, 10, true /*thumbnail*/);\n      } else if (page == Page.LIST) {\n        return FlickrPhotoList.newInstance();\n      } else {\n        throw new IllegalArgumentException(\"No fragment class for page=\" + page);\n      }\n    }\n\n    private int getPageSize(int id) {\n      return getResources().getDimensionPixelSize(id);\n    }\n  }\n\n  private static class BackgroundThumbnailFetcher implements Runnable {\n    private final Context context;\n    private final List<Photo> photos;\n\n    private boolean isCancelled;\n\n    BackgroundThumbnailFetcher(Context context, List<Photo> photos) {\n      this.context = context;\n      this.photos = photos;\n    }\n\n    void cancel() {\n      isCancelled = true;\n    }\n\n    @Override\n    public void run() {\n      Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);\n      for (Photo photo : photos) {\n        if (isCancelled) {\n          return;\n        }\n\n        FutureTarget<File> futureTarget =\n            GlideApp.with(context)\n                .downloadOnly()\n                .load(photo)\n                .submit(Api.SQUARE_THUMB_SIZE, Api.SQUARE_THUMB_SIZE);\n\n        try {\n          futureTarget.get();\n        } catch (InterruptedException e) {\n          if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(TAG, \"Interrupted waiting for background downloadOnly\", e);\n          }\n        } catch (ExecutionException e) {\n          if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(TAG, \"Got ExecutionException waiting for background downloadOnly\", e);\n          }\n        }\n        GlideApp.with(context).clear(futureTarget);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FullscreenActivity.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport static com.bumptech.glide.request.RequestOptions.fitCenterTransform;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.widget.ImageView;\nimport androidx.fragment.app.FragmentActivity;\nimport com.bumptech.glide.samples.flickr.api.Photo;\n\n/** A simple activity for viewing a single photo. */\npublic class FullscreenActivity extends FragmentActivity {\n  private static final String ARG_PHOTO = \"photo\";\n\n  public static Intent getIntent(Context context, Photo photo) {\n    Intent intent = new Intent(context, FullscreenActivity.class);\n    intent.putExtra(ARG_PHOTO, photo);\n    return intent;\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.fullscreen_activity);\n    ImageView fullscreenView = (ImageView) findViewById(R.id.fullscreen_view);\n    Photo photo = getIntent().getParcelableExtra(ARG_PHOTO);\n\n    GlideApp.with(this).load(photo).apply(fitCenterTransform()).into(fullscreenView);\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/PhotoViewer.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport com.bumptech.glide.samples.flickr.api.Photo;\nimport java.util.List;\n\n/**\n * An interface for an object that displays {@link com.bumptech.glide.samples.flickr.api.Photo}\n * objects.\n */\ninterface PhotoViewer {\n  /**\n   * Called whenever new {@link com.bumptech.glide.samples.flickr.api.Photo}s are loaded.\n   *\n   * @param photos The loaded photos.\n   */\n  void onPhotosUpdated(List<Photo> photos);\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/SquareImageView.java",
    "content": "package com.bumptech.glide.samples.flickr;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.widget.ImageView;\nimport androidx.appcompat.widget.AppCompatImageView;\n\n/** An always square {@link ImageView}. */\npublic final class SquareImageView extends AppCompatImageView {\n\n  public SquareImageView(Context context) {\n    super(context);\n  }\n\n  public SquareImageView(Context context, AttributeSet attrs) {\n    super(context, attrs);\n  }\n\n  public SquareImageView(Context context, AttributeSet attrs, int defStyleAttr) {\n    super(context, attrs, defStyleAttr);\n  }\n\n  // We want a square view.\n  @SuppressWarnings(\"SuspiciousNameCombination\")\n  @Override\n  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n    super.onMeasure(widthMeasureSpec, widthMeasureSpec);\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java",
    "content": "package com.bumptech.glide.samples.flickr.api;\n\nimport android.content.Context;\nimport android.util.SparseArray;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.toolbox.StringRequest;\nimport com.android.volley.toolbox.Volley;\nimport com.bumptech.glide.util.LruCache;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/** A class for interfacing with Flickr's http API. */\npublic final class Api {\n  private static Api api;\n  private static final String API_KEY = \"f0e6fbb5fdf1f3842294a1d21f84e8a6\";\n  private static final String SIGNED_API_URL =\n      \"https://api.flickr.com/services/rest/?method=%s&format=json&api_key=\" + API_KEY;\n  // Incomplete size independent url for photos that can be cached per photo\n  private static final String CACHEABLE_PHOTO_URL = \"http://farm%s.staticflickr.com/%s/%s_%s_\";\n  private static final int MAX_URLS_TO_CACHE = 2000;\n  private static final LruCache<UrlCacheKey, String> CACHED_URLS =\n      new LruCache<>(MAX_URLS_TO_CACHE);\n  private static final int MAX_ITEMS_PER_PAGE = 300;\n\n  /**\n   * Safe search is on by default in Flickr's API and/or enabling it isn't very effective. Instead\n   * we'll force all images to be from Flickr's commons project, which tends to be historic images.\n   * Those appear much safer than a standard search.\n   */\n  private static final String SAFE_SEARCH = \"&is_commons=1\";\n\n  private static final String PER_PAGE = \"&per_page=\" + MAX_ITEMS_PER_PAGE;\n\n  private static final SparseArray<String> EDGE_TO_SIZE_KEY =\n      new SparseArray<String>() {\n        {\n          put(75, \"s\");\n          put(100, \"t\");\n          put(150, \"q\");\n          put(240, \"m\");\n          put(320, \"n\");\n          put(640, \"z\");\n          put(1024, \"b\");\n        }\n      };\n  private static final List<Integer> SORTED_SIZE_KEYS = new ArrayList<>(EDGE_TO_SIZE_KEY.size());\n\n  static {\n    for (int i = 0; i < EDGE_TO_SIZE_KEY.size(); i++) {\n      SORTED_SIZE_KEYS.add(EDGE_TO_SIZE_KEY.keyAt(i));\n    }\n    Collections.sort(SORTED_SIZE_KEYS);\n  }\n\n  public static final int SQUARE_THUMB_SIZE = SORTED_SIZE_KEYS.get(0);\n\n  private static String getSizeKey(int width, int height) {\n    final int largestEdge = Math.max(width, height);\n\n    String result = EDGE_TO_SIZE_KEY.get(SORTED_SIZE_KEYS.get(SORTED_SIZE_KEYS.size() - 1));\n    for (int edge : SORTED_SIZE_KEYS) {\n      if (largestEdge <= edge) {\n        result = EDGE_TO_SIZE_KEY.get(edge);\n        break;\n      }\n    }\n    return result;\n  }\n\n  private static List<String> getLargerSizeKeys(int width, int height) {\n    final int largestEdge = Math.max(width, height);\n\n    boolean isFirstLargest = true;\n    List<String> result = new ArrayList<>();\n    int size = SORTED_SIZE_KEYS.size();\n    for (int i = 0; i < size; i++) {\n      int edge = SORTED_SIZE_KEYS.get(i);\n      if (largestEdge <= edge) {\n        if (isFirstLargest) {\n          isFirstLargest = false;\n        } else {\n          result.add(EDGE_TO_SIZE_KEY.get(edge));\n        }\n      }\n    }\n    return result;\n  }\n\n  static String getCacheableUrl(Photo photo) {\n    return String.format(\n        CACHEABLE_PHOTO_URL, photo.getFarm(), photo.getServer(), photo.getId(), photo.getSecret());\n  }\n\n  public static String getPhotoURL(Photo photo, int width, int height) {\n    return getPhotoUrl(photo, getSizeKey(width, height));\n  }\n\n  public static List<String> getAlternateUrls(Photo photo, int width, int height) {\n    List<String> result = new ArrayList<>();\n    for (String sizeKey : getLargerSizeKeys(width, height)) {\n      result.add(getPhotoUrl(photo, sizeKey));\n    }\n    return result;\n  }\n\n  private static String getUrlForMethod(String method) {\n    return String.format(SIGNED_API_URL, method);\n  }\n\n  private static String getPhotoUrl(Photo photo, String sizeKey) {\n    UrlCacheKey entry = new UrlCacheKey(photo, sizeKey);\n    String result = CACHED_URLS.get(entry);\n    if (result == null) {\n      result = photo.getPartialUrl() + sizeKey + \".jpg\";\n      CACHED_URLS.put(entry, result);\n    }\n    return result;\n  }\n\n  static String getSearchUrl(String text, boolean requireSafeOverQuality) {\n    return getUrlForMethod(\"flickr.photos.search\")\n        + \"&text=\"\n        + text\n        + PER_PAGE\n        + (requireSafeOverQuality ? SAFE_SEARCH : \"\");\n  }\n\n  static String getRecentUrl() {\n    return getUrlForMethod(\"flickr.photos.getRecent\" + PER_PAGE);\n  }\n\n  /** An interface for listening for search results from the Flickr API. */\n  public interface QueryListener {\n    /**\n     * Called when a search completes successfully.\n     *\n     * @param query The query used to obtain the results.\n     * @param photos A list of images that were found for the given search term.\n     */\n    void onSearchCompleted(Query query, List<Photo> photos);\n\n    /**\n     * Called when a search fails.\n     *\n     * @param query The query we attempted to obtain results for.\n     * @param e The exception that caused the search to fail.\n     */\n    void onSearchFailed(Query query, Exception e);\n  }\n\n  public static Api get(Context context) {\n    if (api == null) {\n      api = new Api(context);\n    }\n    return api;\n  }\n\n  private final RequestQueue requestQueue;\n  private final Set<QueryListener> queryListeners = new HashSet<>();\n  private QueryResult lastQueryResult;\n\n  private Api(Context context) {\n    this.requestQueue = Volley.newRequestQueue(context.getApplicationContext());\n    QueryListener queryListener =\n        new QueryListener() {\n          @Override\n          public void onSearchCompleted(Query query, List<Photo> photos) {\n            lastQueryResult = new QueryResult(query, photos);\n          }\n\n          @Override\n          public void onSearchFailed(Query query, Exception e) {\n            lastQueryResult = null;\n          }\n        };\n    queryListeners.add(queryListener);\n  }\n\n  public void registerSearchListener(QueryListener queryListener) {\n    queryListeners.add(queryListener);\n  }\n\n  public void unregisterSearchListener(QueryListener queryListener) {\n    queryListeners.remove(queryListener);\n  }\n\n  public void query(Query query) {\n    if (lastQueryResult != null && lastQueryResult.query.equals(query)) {\n      for (QueryListener listener : queryListeners) {\n        listener.onSearchCompleted(lastQueryResult.query, lastQueryResult.results);\n      }\n      return;\n    }\n\n    FlickrQueryResponseListener responseListener =\n        new FlickrQueryResponseListener(new PhotoJsonStringParser(), query, queryListeners);\n    StringRequest request =\n        new StringRequest(Request.Method.GET, query.getUrl(), responseListener, responseListener);\n    request.setRetryPolicy(\n        new DefaultRetryPolicy(\n            DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 3, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n    requestQueue.add(request);\n  }\n\n  private static class QueryResult {\n    private final Query query;\n    private final List<Photo> results;\n\n    QueryResult(Query query, List<Photo> results) {\n      this.query = query;\n      this.results = results;\n    }\n  }\n\n  private static final class UrlCacheKey {\n    private final Photo photo;\n    private final String sizeKey;\n\n    private UrlCacheKey(Photo photo, String sizeKey) {\n      this.photo = photo;\n      this.sizeKey = sizeKey;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (o instanceof UrlCacheKey) {\n        UrlCacheKey other = (UrlCacheKey) o;\n        return photo.equals(other.photo) && sizeKey.equals(other.sizeKey);\n      }\n      return false;\n    }\n\n    @Override\n    public int hashCode() {\n      int result = photo.hashCode();\n      result = 31 * result + sizeKey.hashCode();\n      return result;\n    }\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/FlickrQueryResponseListener.java",
    "content": "package com.bumptech.glide.samples.flickr.api;\n\nimport com.android.volley.Response;\nimport com.android.volley.VolleyError;\nimport java.util.Collection;\nimport java.util.List;\nimport org.json.JSONException;\n\n/** Handles photo list responses and errors from Flickr API calls. */\nfinal class FlickrQueryResponseListener\n    implements Response.Listener<String>, Response.ErrorListener {\n  private final PhotoJsonStringParser parser;\n  private final Query query;\n  private final Collection<Api.QueryListener> listeners;\n\n  FlickrQueryResponseListener(\n      PhotoJsonStringParser parser, Query query, Collection<Api.QueryListener> listeners) {\n    this.parser = parser;\n    this.query = query;\n    this.listeners = listeners;\n  }\n\n  @Override\n  public void onResponse(String response) {\n    try {\n      notifySuccess(parser.parse(response));\n    } catch (JSONException e) {\n      notifyFailed(e);\n    }\n  }\n\n  @Override\n  public void onErrorResponse(VolleyError error) {\n    notifyFailed(error);\n  }\n\n  private void notifySuccess(List<Photo> results) {\n    for (Api.QueryListener listener : listeners) {\n      listener.onSearchCompleted(query, results);\n    }\n  }\n\n  private void notifyFailed(Exception e) {\n    for (Api.QueryListener listener : listeners) {\n      listener.onSearchFailed(query, e);\n    }\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Photo.java",
    "content": "package com.bumptech.glide.samples.flickr.api;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/** A POJO representing a JSON object returned from Flickr's api representing a single image. */\npublic class Photo implements Parcelable {\n  public static final Creator<Photo> CREATOR =\n      new Creator<Photo>() {\n        @Override\n        public Photo createFromParcel(Parcel parcel) {\n          return new Photo(parcel);\n        }\n\n        @Override\n        public Photo[] newArray(int i) {\n          return new Photo[i];\n        }\n      };\n\n  private final String id;\n  private final String owner;\n  private final String title;\n  private final String server;\n  private final String farm;\n  private final String secret;\n  private String partialUrl = null;\n\n  public Photo(JSONObject jsonPhoto) throws JSONException {\n    this.id = jsonPhoto.getString(\"id\");\n    this.owner = jsonPhoto.getString(\"owner\");\n    this.title = jsonPhoto.optString(\"title\", \"\");\n    this.server = jsonPhoto.getString(\"server\");\n    this.farm = jsonPhoto.getString(\"farm\");\n    this.secret = jsonPhoto.getString(\"secret\");\n  }\n\n  private Photo(Parcel in) {\n    id = in.readString();\n    owner = in.readString();\n    title = in.readString();\n    server = in.readString();\n    farm = in.readString();\n    secret = in.readString();\n  }\n\n  @Override\n  public void writeToParcel(Parcel parcel, int i) {\n    parcel.writeString(id);\n    parcel.writeString(owner);\n    parcel.writeString(title);\n    parcel.writeString(server);\n    parcel.writeString(farm);\n    parcel.writeString(secret);\n  }\n\n  public String getPartialUrl() {\n    if (partialUrl == null) {\n      partialUrl = Api.getCacheableUrl(this);\n    }\n    return partialUrl;\n  }\n\n  public String getId() {\n    return id;\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public String getServer() {\n    return server;\n  }\n\n  public String getFarm() {\n    return farm;\n  }\n\n  public String getSecret() {\n    return secret;\n  }\n\n  @Override\n  public String toString() {\n    return getPartialUrl();\n  }\n\n  @SuppressWarnings({\"PMD.SimplifyBooleanReturns\", \"RedundantIfStatement\"})\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    Photo photo = (Photo) o;\n\n    if (!farm.equals(photo.farm)) {\n      return false;\n    }\n    if (!id.equals(photo.id)) {\n      return false;\n    }\n    if (!owner.equals(photo.owner)) {\n      return false;\n    }\n    if (!secret.equals(photo.secret)) {\n      return false;\n    }\n    if (!server.equals(photo.server)) {\n      return false;\n    }\n    if (!title.equals(photo.title)) {\n      return false;\n    }\n\n    return true;\n  }\n\n  @Override\n  public int hashCode() {\n    int result = id.hashCode();\n    result = 31 * result + owner.hashCode();\n    result = 31 * result + title.hashCode();\n    result = 31 * result + server.hashCode();\n    result = 31 * result + farm.hashCode();\n    result = 31 * result + secret.hashCode();\n    return result;\n  }\n\n  @Override\n  public int describeContents() {\n    return 0;\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/PhotoJsonStringParser.java",
    "content": "package com.bumptech.glide.samples.flickr.api;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/**\n * Parses a list of {@link Photo} objects from a Flickr API response string containing JSON data.\n */\nfinal class PhotoJsonStringParser {\n  private static final int FLICKR_API_PREFIX_LENGTH = 14;\n\n  List<Photo> parse(String response) throws JSONException {\n    JSONObject searchResults =\n        new JSONObject(response.substring(FLICKR_API_PREFIX_LENGTH, response.length() - 1));\n    JSONArray photos = searchResults.getJSONObject(\"photos\").getJSONArray(\"photo\");\n    List<Photo> results = new ArrayList<>(photos.length());\n    for (int i = 0, size = photos.length(); i < size; i++) {\n      results.add(new Photo(photos.getJSONObject(i)));\n    }\n\n    return results;\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Query.java",
    "content": "package com.bumptech.glide.samples.flickr.api;\n\nimport android.os.Parcelable;\n\n/** An interface representing a query in Flickr's API that returns a list of photos. */\npublic interface Query extends Parcelable {\n  /** A user facing description of the query. */\n  String getDescription();\n\n  /** The url to use to execute the query. */\n  String getUrl();\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/RecentQuery.java",
    "content": "package com.bumptech.glide.samples.flickr.api;\n\nimport android.os.Parcel;\n\n/** Query using Flickr's recent API. */\npublic final class RecentQuery implements Query {\n  public static final Creator<RecentQuery> CREATOR =\n      new Creator<RecentQuery>() {\n        @Override\n        public RecentQuery createFromParcel(Parcel source) {\n          return RECENT_QUERY;\n        }\n\n        @Override\n        public RecentQuery[] newArray(int size) {\n          return new RecentQuery[size];\n        }\n      };\n  private static final RecentQuery RECENT_QUERY = new RecentQuery();\n\n  public static RecentQuery get() {\n    return RECENT_QUERY;\n  }\n\n  private RecentQuery() {\n    // Singleton.\n  }\n\n  @Override\n  public String getDescription() {\n    return \"Recent\";\n  }\n\n  @Override\n  public String getUrl() {\n    return Api.getRecentUrl();\n  }\n\n  @Override\n  public int describeContents() {\n    return 0;\n  }\n\n  @Override\n  public void writeToParcel(Parcel dest, int flags) {}\n}\n"
  },
  {
    "path": "samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/SearchQuery.java",
    "content": "package com.bumptech.glide.samples.flickr.api;\n\nimport android.os.Parcel;\n\n/** Wraps a search query string. */\npublic final class SearchQuery implements Query {\n  public static final Creator<SearchQuery> CREATOR =\n      new Creator<SearchQuery>() {\n        @Override\n        public SearchQuery createFromParcel(Parcel source) {\n          return new SearchQuery(source);\n        }\n\n        @Override\n        public SearchQuery[] newArray(int size) {\n          return new SearchQuery[size];\n        }\n      };\n\n  private final String queryString;\n  private boolean requireSafeOverQuality;\n\n  public SearchQuery(String queryString) {\n    this.queryString = queryString;\n  }\n\n  /**\n   * Requires the search to be as safe as possible, evne if it substantially limits the results in a\n   * way that might otherwise be unexpected.\n   */\n  public SearchQuery requireSafeOverQuality() {\n    requireSafeOverQuality = true;\n    return this;\n  }\n\n  private SearchQuery(Parcel in) {\n    queryString = in.readString();\n  }\n\n  @Override\n  public void writeToParcel(Parcel dest, int flags) {\n    dest.writeString(queryString);\n  }\n\n  @Override\n  public int describeContents() {\n    return 0;\n  }\n\n  @Override\n  public String getDescription() {\n    return queryString;\n  }\n\n  @Override\n  public String getUrl() {\n    return Api.getSearchUrl(queryString, requireSafeOverQuality);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o instanceof SearchQuery) {\n      SearchQuery other = (SearchQuery) o;\n      return queryString.equals(other.queryString);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return queryString.hashCode();\n  }\n}\n"
  },
  {
    "path": "samples/flickr/src/main/res/layout/flickr_photo_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingTop=\"10dp\"\n    android:id=\"@+id/flickr_photo_grid\" />\n"
  },
  {
    "path": "samples/flickr/src/main/res/layout/flickr_photo_grid_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.bumptech.glide.samples.flickr.SquareImageView\n       xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:scaleType=\"centerCrop\"\n       android:layout_width=\"match_parent\"\n       android:layout_height=\"match_parent\"\n       android:contentDescription=\"@string/image_description\"/>\n"
  },
  {
    "path": "samples/flickr/src/main/res/layout/flickr_photo_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:id=\"@+id/flickr_photo_list\" />\n\n"
  },
  {
    "path": "samples/flickr/src/main/res/layout/flickr_photo_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:orientation=\"horizontal\"\n              android:layout_width=\"fill_parent\"\n              android:layout_height=\"@dimen/flickr_list_item_height\"\n              android:padding=\"10dp\">\n\n    <ImageView\n        android:id=\"@+id/photo_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"fill_parent\"\n        android:layout_weight=\"1\"\n        android:scaleType=\"centerCrop\"\n        android:contentDescription=\"@string/image_description\"/>\n\n    <TextView\n        android:id=\"@+id/title_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"fill_parent\"\n        android:layout_weight=\"1\"\n        android:layout_margin=\"10dp\"\n        android:gravity=\"center\"\n        android:textSize=\"16sp\"/>\n</LinearLayout>\n"
  },
  {
    "path": "samples/flickr/src/main/res/layout/flickr_search_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/view_pager\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\">\n\n        <androidx.viewpager.widget.PagerTabStrip\n                android:id=\"@+id/pager_tab_strip\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"top\" />\n\n    </androidx.viewpager.widget.ViewPager>\n\n    <LinearLayout\n        android:id=\"@+id/searching\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\"\n        android:background=\"@color/translucent_black\"\n        android:visibility=\"invisible\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/search_term\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:textSize=\"16sp\"\n            android:layout_marginBottom=\"10dp\"/>\n\n        <ProgressBar\n            android:id=\"@+id/search_loading\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:indeterminate=\"true\"/>\n    </LinearLayout>\n</merge>\n"
  },
  {
    "path": "samples/flickr/src/main/res/layout/fullscreen_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n           android:id=\"@+id/fullscreen_view\"\n           android:layout_width=\"match_parent\"\n           android:layout_height=\"match_parent\"\n           android:contentDescription=\"@string/fullscreen_description\"\n           />\n"
  },
  {
    "path": "samples/flickr/src/main/res/menu/search_activity.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<item android:id=\"@+id/search\"\n      android:title=\"@string/search\"\n      android:icon=\"@android:drawable/ic_menu_search\"\n      app:showAsAction=\"collapseActionView|ifRoom\"\n      app:actionViewClass=\"android.widget.SearchView\" />\n</menu>\n"
  },
  {
    "path": "samples/flickr/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"translucent_black\">#B0000000</color>\n</resources>\n"
  },
  {
    "path": "samples/flickr/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"medium_photo_side\">110dp</dimen>\n    <dimen name=\"small_photo_side\">55dp</dimen>\n    <dimen name=\"flickr_list_item_height\">170dp</dimen>\n    <dimen name=\"grid_margin\">5dp</dimen>\n    <dimen name=\"page_margin\">8dp</dimen>\n</resources>\n\n"
  },
  {
    "path": "samples/flickr/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"app_name\">Glide Flickr Demo</string>\n  <string name=\"search\">Search</string>\n  <string name=\"searching_for\">Searching for \\\"%s\\\" on Flickr</string>\n  <string name=\"search_failed\">Search for \\\"%s\\\" failed, check your network connection and try again</string>\n  <string name=\"small\">Small</string>\n  <string name=\"medium\">Medium</string>\n  <string name=\"list\">List</string>\n  <string name=\"image_description\">An image from the list of results for a search query</string>\n  <string name=\"fullscreen_description\">Full screen view of an image</string>\n</resources>\n"
  },
  {
    "path": "samples/flickr/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\">staticflickr.com</domain>\n  </domain-config>\n</network-security-config>\n"
  },
  {
    "path": "samples/gallery/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n    id(\"org.jetbrains.kotlin.android\")\n    id(\"org.jetbrains.kotlin.plugin.parcelize\")\n    id(\"com.google.devtools.ksp\")\n}\n\nkotlin {\n    jvmToolchain {\n        languageVersion.set(JavaLanguageVersion.of(11))\n    }\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.samples.gallery\"\n    compileSdk = 34\n\n    defaultConfig {\n        minSdk = 29\n        versionCode = 1\n        versionName = \"1.0\"\n    }\n    buildFeatures {\n        compose = true\n    }\n    kotlinOptions {\n        jvmTarget = \"11\"\n    }\n    composeOptions {\n        kotlinCompilerExtensionVersion = libs.versions.kotlin.compiler.extension.get()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    implementation(project(\":integration:compose\"))\n    implementation(project(\":integration:ktx\"))\n\n    implementation(project(\":integration:recyclerview\")) {\n        isTransitive = false\n    }\n\n    implementation(libs.androidx.recyclerview)\n    implementation(libs.androidx.fragment.ktx)\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.coroutines.core)\n    implementation(libs.coroutines.android)\n    implementation(libs.kotlin.jdk7)\n    implementation(libs.compose.foundation)\n    implementation(libs.compose.ui)\n\n    ksp(project(\":annotation:ksp\"))\n}"
  },
  {
    "path": "samples/gallery/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <!-- Not supported by all build systems -->\n    <issue id=\"GradleOverrides\" severity=\"ignore\" />\n    <issue id=\"IconMissingDensityFolder\" severity=\"ignore\"/>\n    <issue id=\"GoogleAppIndexingWarning\" severity=\"ignore\"/>\n    <issue id=\"GradleDependency\" severity=\"ignore\" />\n    <issue id=\"Autofill\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "samples/gallery/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <uses-sdk\n    android:minSdkVersion=\"29\"\n    android:targetSdkVersion=\"34\"\n    android:compileSdkVersion=\"34\" />\n\n  <uses-permission\n    android:maxSdkVersion=\"32\"\n    android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n  <uses-permission\n    android:name=\"android.permission.READ_MEDIA_IMAGES\" />\n  <uses-permission android:name=\"android.permission.READ_MEDIA_VIDEO\" />\n\n  <application\n          android:label=\"@string/app_name\"\n          android:icon=\"@android:drawable/sym_def_app_icon\"\n          android:allowBackup=\"false\">\n    <activity android:name=\".MainActivity\"\n              android:exported=\"true\"\n              android:label=\"@string/app_name\">\n      <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\"/>\n        <category android:name=\"android.intent.category.LAUNCHER\"/>\n      </intent-filter>\n    </activity>\n  </application>\n</manifest>\n"
  },
  {
    "path": "samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/GalleryModule.kt",
    "content": "package com.bumptech.glide.samples.gallery\n\nimport android.content.Context\nimport com.bumptech.glide.GlideBuilder\nimport com.bumptech.glide.annotation.GlideModule\nimport com.bumptech.glide.module.AppGlideModule\n\n/** Ensures that Glide's generated API is created for the Gallery sample.  */\n@GlideModule\nclass GalleryModule : AppGlideModule() {\n  override fun applyOptions(context: Context, builder: GlideBuilder) {\n    super.applyOptions(context, builder)\n    builder.setIsActiveResourceRetentionAllowed(true)\n  }\n}\n"
  },
  {
    "path": "samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/GalleryViewModel.kt",
    "content": "package com.bumptech.glide.samples.gallery\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\nclass GalleryViewModel(application: Application) : AndroidViewModel(application) {\n  private val mediaStoreDataSource = MediaStoreDataSource(application)\n\n  private val _uiState: MutableStateFlow<List<MediaStoreData>> = MutableStateFlow(emptyList())\n  val mediaStoreData: StateFlow<List<MediaStoreData>> = _uiState\n\n  init {\n    viewModelScope.launch {\n      mediaStoreDataSource.loadMediaStoreData().flowOn(Dispatchers.IO).collect {\n        _uiState.value = it\n      }\n    }\n  }\n}"
  },
  {
    "path": "samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/HorizontalGalleryFragment.kt",
    "content": "package com.bumptech.glide.samples.gallery\n\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport androidx.fragment.app.Fragment\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.platform.ComposeView\nimport androidx.compose.ui.unit.dp\nimport androidx.fragment.app.viewModels\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi\nimport com.bumptech.glide.integration.compose.GlideImage\nimport com.bumptech.glide.integration.compose.rememberGlidePreloadingData\nimport com.bumptech.glide.signature.MediaStoreSignature\n\n/** Displays media store data in a recycler view. */\n@OptIn(ExperimentalGlideComposeApi::class)\nclass HorizontalGalleryFragment : Fragment() {\n\n  override fun onCreateView(\n    inflater: LayoutInflater,\n    container: ViewGroup?,\n    savedInstanceState: Bundle?,\n  ): View {\n    val galleryViewModel: GalleryViewModel by viewModels()\n    return ComposeView(requireContext()).apply {\n      setContent { LoadableDeviceMedia(galleryViewModel) }\n    }\n  }\n\n  @Composable\n  fun LoadableDeviceMedia(viewModel: GalleryViewModel) {\n    val mediaStoreData = viewModel.mediaStoreData.collectAsState()\n    DeviceMedia(mediaStoreData.value)\n  }\n\n  @Composable\n  fun DeviceMedia(mediaStoreData: List<MediaStoreData>) {\n    val requestBuilderTransform =\n      { item: MediaStoreData, requestBuilder: RequestBuilder<Drawable> ->\n        requestBuilder.load(item.uri).signature(item.signature())\n      }\n\n    val preloadingData =\n      rememberGlidePreloadingData(\n        mediaStoreData,\n        THUMBNAIL_SIZE,\n        requestBuilderTransform = requestBuilderTransform,\n      )\n\n    LazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {\n      items(preloadingData.size) { index ->\n        val (mediaStoreItem, preloadRequestBuilder) = preloadingData[index]\n        MediaStoreView(mediaStoreItem, preloadRequestBuilder, Modifier.fillParentMaxSize())\n      }\n    }\n  }\n\n  private fun MediaStoreData.signature() = MediaStoreSignature(mimeType, dateModified, orientation)\n\n  @Composable\n  fun MediaStoreView(\n    item: MediaStoreData,\n    preloadRequestBuilder: RequestBuilder<Drawable>,\n    modifier: Modifier,\n  ) =\n    GlideImage(model = item.uri, contentDescription = item.displayName, modifier = modifier) {\n      it.thumbnail(preloadRequestBuilder).signature(item.signature())\n    }\n\n  companion object {\n    private const val THUMBNAIL_DIMENSION = 50\n    private val THUMBNAIL_SIZE = Size(THUMBNAIL_DIMENSION.toFloat(), THUMBNAIL_DIMENSION.toFloat())\n  }\n}\n"
  },
  {
    "path": "samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/MainActivity.kt",
    "content": "package com.bumptech.glide.samples.gallery\n\nimport android.Manifest\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.MemoryCategory\n\n/** Displays a [HorizontalGalleryFragment].  */\nclass MainActivity : FragmentActivity() {\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    setContentView(R.layout.main_activity)\n    Glide.get(this).setMemoryCategory(MemoryCategory.HIGH)\n    if (PERMISSIONS_REQUEST.any {\n        ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED\n      }) {\n      requestStoragePermission()\n    } else {\n      replaceFragment()\n    }\n  }\n\n  private fun requestStoragePermission() {\n    ActivityCompat.requestPermissions(\n      this, PERMISSIONS_REQUEST, REQUEST_READ_STORAGE)\n  }\n\n  private fun replaceFragment() {\n    val fragment: Fragment = HorizontalGalleryFragment()\n    supportFragmentManager\n      .beginTransaction()\n      .replace(R.id.fragment_container, fragment)\n      .commit()\n  }\n\n  override fun onRequestPermissionsResult(\n    requestCode: Int, permissions: Array<String>, grantResults: IntArray,\n  ) {\n    super.onRequestPermissionsResult(requestCode, permissions, grantResults)\n    when (requestCode) {\n      REQUEST_READ_STORAGE -> {\n        // If request is cancelled, the result arrays are empty.\n        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n          replaceFragment()\n        } else {\n          Toast.makeText(this, \"Storage permission is required\", Toast.LENGTH_LONG).show()\n          requestStoragePermission()\n        }\n      }\n    }\n  }\n\n  companion object {\n    private const val REQUEST_READ_STORAGE = 0\n    private val PERMISSIONS_REQUEST =\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n        arrayOf(Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO)\n      } else {\n        arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)\n      }\n  }\n}"
  },
  {
    "path": "samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/MediaStoreDataSource.kt",
    "content": "package com.bumptech.glide.samples.gallery\n\nimport android.content.Context\nimport android.database.ContentObserver\nimport android.net.Uri\nimport android.os.Handler\nimport android.os.Looper\nimport android.os.Parcelable\nimport android.provider.MediaStore\nimport android.provider.MediaStore.Files.FileColumns\nimport android.provider.MediaStore.MediaColumns\nimport com.bumptech.glide.util.Preconditions\nimport com.bumptech.glide.util.Util\nimport java.util.ArrayList\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.parcelize.Parcelize\n\n/** Loads metadata from the media store for images and videos. */\nclass MediaStoreDataSource\ninternal constructor(\n  private val context: Context,\n) {\n\n  fun loadMediaStoreData(): Flow<List<MediaStoreData>> = callbackFlow {\n    val contentObserver =\n      object : ContentObserver(Handler(Looper.getMainLooper())) {\n        override fun onChange(selfChange: Boolean) {\n          super.onChange(selfChange)\n          launch { trySend(query()) }\n        }\n      }\n\n    context.contentResolver.registerContentObserver(\n      MEDIA_STORE_FILE_URI,\n      /* notifyForDescendants=*/ true,\n      contentObserver\n    )\n\n    trySend(query())\n\n    awaitClose { context.contentResolver.unregisterContentObserver(contentObserver) }\n  }\n\n  private fun query(): MutableList<MediaStoreData> {\n    Preconditions.checkArgument(\n      Util.isOnBackgroundThread(),\n      \"Can only query from a background thread\"\n    )\n    val data: MutableList<MediaStoreData> = ArrayList()\n    val cursor =\n      context.contentResolver.query(\n        MEDIA_STORE_FILE_URI,\n        PROJECTION,\n        FileColumns.MEDIA_TYPE +\n          \" = \" +\n          FileColumns.MEDIA_TYPE_IMAGE +\n          \" OR \" +\n          FileColumns.MEDIA_TYPE +\n          \" = \" +\n          FileColumns.MEDIA_TYPE_VIDEO,\n        /* selectionArgs= */ null,\n        \"${MediaColumns.DATE_TAKEN} DESC\"\n      )\n        ?: return data\n\n    @Suppress(\"NAME_SHADOWING\") // Might as well, it's the same object?\n    cursor.use { cursor ->\n      val idColNum = cursor.getColumnIndexOrThrow(MediaColumns._ID)\n      val dateTakenColNum = cursor.getColumnIndexOrThrow(MediaColumns.DATE_TAKEN)\n      val dateModifiedColNum = cursor.getColumnIndexOrThrow(MediaColumns.DATE_MODIFIED)\n      val mimeTypeColNum = cursor.getColumnIndexOrThrow(MediaColumns.MIME_TYPE)\n      val orientationColNum = cursor.getColumnIndexOrThrow(MediaColumns.ORIENTATION)\n      val mediaTypeColumnIndex = cursor.getColumnIndexOrThrow(FileColumns.MEDIA_TYPE)\n      val displayNameIndex = cursor.getColumnIndexOrThrow(FileColumns.DISPLAY_NAME)\n\n      while (cursor.moveToNext()) {\n        val id = cursor.getLong(idColNum)\n        val dateTaken = cursor.getLong(dateTakenColNum)\n        val mimeType = cursor.getString(mimeTypeColNum)\n        val dateModified = cursor.getLong(dateModifiedColNum)\n        val orientation = cursor.getInt(orientationColNum)\n        val displayName = cursor.getString(displayNameIndex)\n        val type =\n          if (cursor.getInt(mediaTypeColumnIndex) == FileColumns.MEDIA_TYPE_IMAGE) Type.IMAGE\n          else Type.VIDEO\n        data.add(\n          MediaStoreData(\n            type = type,\n            rowId = id,\n            uri = Uri.withAppendedPath(MEDIA_STORE_FILE_URI, id.toString()),\n            mimeType = mimeType,\n            dateModified = dateModified,\n            orientation = orientation,\n            dateTaken = dateTaken,\n            displayName = displayName,\n          )\n        )\n      }\n    }\n    return data\n  }\n\n  companion object {\n    private val MEDIA_STORE_FILE_URI = MediaStore.Files.getContentUri(\"external\")\n    private val PROJECTION =\n      arrayOf(\n        MediaColumns._ID,\n        MediaColumns.DATE_TAKEN,\n        MediaColumns.DATE_MODIFIED,\n        MediaColumns.MIME_TYPE,\n        MediaColumns.ORIENTATION,\n        MediaColumns.DISPLAY_NAME,\n        FileColumns.MEDIA_TYPE\n      )\n  }\n}\n\n/** A data model containing data for a single media item. */\n@Parcelize\ndata class MediaStoreData(\n  private val type: Type,\n  val rowId: Long,\n  val uri: Uri,\n  val mimeType: String?,\n  val dateModified: Long,\n  val orientation: Int,\n  val dateTaken: Long,\n  val displayName: String?\n) : Parcelable\n\n/** The type of data. */\nenum class Type {\n  VIDEO,\n  IMAGE\n}\n"
  },
  {
    "path": "samples/gallery/src/main/res/layout/main_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  tools:ignore=\"MergeRootFrame\"\n  android:id=\"@+id/fragment_container\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\" />\n"
  },
  {
    "path": "samples/gallery/src/main/res/layout/recycler_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n   android:id=\"@+id/image\"\n   android:paddingLeft=\"5dp\"\n   android:paddingRight=\"5dp\"\n   android:layout_width=\"match_parent\"\n   android:layout_height=\"match_parent\"\n   android:contentDescription=\"@string/image_content_description\"/>\n"
  },
  {
    "path": "samples/gallery/src/main/res/layout/recycler_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/recycler_view\"\n    android:paddingBottom=\"20dp\"\n    android:paddingTop=\"20dp\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n"
  },
  {
    "path": "samples/gallery/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <item type=\"id\" name=\"loader_id_media_store_data\" />\n</resources>\n"
  },
  {
    "path": "samples/gallery/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"app_name\">gallery</string>\n  <string name=\"image_content_description\">Photo</string>\n</resources>\n"
  },
  {
    "path": "samples/giphy/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.samples.giphy\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n        \n        versionCode = 1\n        versionName = \"1.0\"\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n\n    implementation(project(\":integration:recyclerview\")) {\n        isTransitive = false\n    }\n\n    implementation(libs.gson)\n\n    implementation(libs.androidx.recyclerview)\n    implementation(libs.androidx.fragment)\n    annotationProcessor(project(\":annotation:compiler\"))\n}"
  },
  {
    "path": "samples/giphy/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <!-- Not supported by all build systems -->\n    <issue id=\"GradleOverrides\" severity=\"ignore\" />\n    <issue id=\"GoogleAppIndexingWarning\" severity=\"ignore\"/>\n    <issue id=\"GradleDependency\" severity=\"ignore\" />\n    <issue id=\"Autofill\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "samples/giphy/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n    <!--\n    Allows Glide to monitor connectivity status and restart failed requests if users go from a\n    a disconnected to a connected network state.\n    -->\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>\n\n    <application\n        android:allowBackup=\"false\"\n        android:icon=\"@android:drawable/sym_def_app_icon\"\n        android:largeHeap=\"true\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".MainActivity\"\n            android:exported=\"true\">\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:exported=\"false\"\n            android:name=\".FullscreenActivity\"/>\n    </application>\n</manifest>\n"
  },
  {
    "path": "samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/Api.java",
    "content": "package com.bumptech.glide.samples.giphy;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport com.bumptech.glide.util.Util;\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Arrays;\nimport java.util.HashSet;\n\n/** A java wrapper for Giphy's http api based on https://github.com/Giphy/GiphyAPI. */\npublic final class Api {\n  private static volatile Api api = null;\n  private static final String BETA_KEY = \"dc6zaTOxFJmzC\";\n  private static final String BASE_URL = \"https://api.giphy.com/\";\n  private static final String TRENDING_PATH = \"v1/gifs/trending\";\n  private static final int LIMIT = 100;\n  private static final int OFFSET = 0;\n  private final Handler bgHandler;\n  private final Handler mainHandler;\n  private final HashSet<Monitor> monitors = new HashSet<>();\n\n  private static String signUrl(String url) {\n    return url + \"&api_key=\" + BETA_KEY;\n  }\n\n  private static String getTrendingUrl() {\n    return signUrl(BASE_URL + TRENDING_PATH + \"?limit=\" + LIMIT + \"&offset=\" + OFFSET);\n  }\n\n  /** An interface for listening for search results. */\n  public interface Monitor {\n    /**\n     * Called when a search completes.\n     *\n     * @param result The results returned from Giphy's search api.\n     */\n    void onSearchComplete(SearchResult result);\n  }\n\n  static Api get() {\n    if (api == null) {\n      synchronized (Api.class) {\n        if (api == null) {\n          api = new Api();\n        }\n      }\n    }\n    return api;\n  }\n\n  private Api() {\n    HandlerThread bgThread = new HandlerThread(\"api_thread\");\n    bgThread.start();\n    bgHandler = new Handler(bgThread.getLooper());\n    mainHandler = new Handler(Looper.getMainLooper());\n    // Do nothing.\n  }\n\n  void addMonitor(Monitor monitor) {\n    monitors.add(monitor);\n  }\n\n  void removeMonitor(Monitor monitor) {\n    monitors.remove(monitor);\n  }\n\n  void getTrending() {\n    String trendingUrl = getTrendingUrl();\n    query(trendingUrl);\n  }\n\n  private void query(final String apiUrl) {\n    bgHandler.post(\n        new Runnable() {\n          @Override\n          public void run() {\n            URL url;\n            try {\n              url = new URL(apiUrl);\n            } catch (MalformedURLException e) {\n              throw new RuntimeException(e);\n            }\n\n            HttpURLConnection urlConnection = null;\n            InputStream is = null;\n            SearchResult result = new SearchResult();\n            try {\n              urlConnection = (HttpURLConnection) url.openConnection();\n              is = urlConnection.getInputStream();\n              InputStreamReader reader = new InputStreamReader(is);\n              result = new Gson().fromJson(reader, SearchResult.class);\n            } catch (IOException e) {\n              e.printStackTrace();\n            } finally {\n              if (is != null) {\n                try {\n                  is.close();\n                } catch (IOException e) {\n                  // Do nothing.\n                }\n              }\n              if (urlConnection != null) {\n                urlConnection.disconnect();\n              }\n            }\n\n            final SearchResult finalResult = result;\n            mainHandler.post(\n                new Runnable() {\n                  @Override\n                  public void run() {\n                    for (Monitor monitor : monitors) {\n                      monitor.onSearchComplete(finalResult);\n                    }\n                  }\n                });\n          }\n        });\n  }\n\n  /** A POJO mirroring the top level result JSON object returned from Giphy's api. */\n  public static final class SearchResult {\n    public GifResult[] data;\n\n    @Override\n    public String toString() {\n      return \"SearchResult{\" + \"data=\" + Arrays.toString(data) + '}';\n    }\n  }\n\n  /**\n   * A POJO mirroring an individual GIF image returned from Giphy's api.\n   *\n   * <p>Implements equals and hashcode so that in memory caching will work when this object is used\n   * as a model for loading Glide's images.\n   */\n  public static final class GifResult {\n    public String id;\n    GifUrlSet images;\n\n    @Override\n    public int hashCode() {\n      int result = id != null ? id.hashCode() : 17;\n      result = 31 * result + (images != null ? images.hashCode() : 17);\n      return result;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n      if (obj instanceof GifResult) {\n        GifResult other = (GifResult) obj;\n        return Util.bothNullOrEqual(id, other.id) && Util.bothNullOrEqual(images, other.images);\n      }\n      return false;\n    }\n\n    @Override\n    public String toString() {\n      return \"GifResult{\" + \"id='\" + id + '\\'' + \", images=\" + images + '}';\n    }\n  }\n\n  /**\n   * A POJO mirroring a JSON object with a put of urls of different sizes and dimensions returned\n   * for a single image from Giphy's api.\n   */\n  public static final class GifUrlSet {\n    GifImage original;\n    GifImage fixed_width;\n    GifImage fixed_height;\n\n    @Override\n    public int hashCode() {\n      int result = original != null ? original.hashCode() : 17;\n      result = 31 * result + (fixed_width != null ? fixed_width.hashCode() : 17);\n      result = 31 * result + (fixed_height != null ? fixed_height.hashCode() : 17);\n      return result;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n      if (obj instanceof GifUrlSet) {\n        GifUrlSet other = (GifUrlSet) obj;\n        return Util.bothNullOrEqual(original, other.original)\n            && Util.bothNullOrEqual(fixed_width, other.fixed_width)\n            && Util.bothNullOrEqual(fixed_height, other.fixed_height);\n      }\n      return false;\n    }\n\n    @Override\n    public String toString() {\n      return \"GifUrlSet{\"\n          + \"original=\"\n          + original\n          + \", fixed_width=\"\n          + fixed_width\n          + \", fixed_height=\"\n          + fixed_height\n          + '}';\n    }\n  }\n\n  /**\n   * A POJO mirroring a JSON object for an image with one particular url, size and dimension\n   * returned from Giphy's api.\n   */\n  public static final class GifImage {\n    String url;\n    int width;\n    int height;\n\n    @Override\n    public int hashCode() {\n      int result = url != null ? url.hashCode() : 17;\n      result = 31 * result + width;\n      result = 31 * result + height;\n      return result;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n      if (obj instanceof GifImage) {\n        GifImage other = (GifImage) obj;\n        return other.width == width\n            && other.height == height\n            && Util.bothNullOrEqual(url, other.url);\n      }\n      return false;\n    }\n\n    public String toString() {\n      return \"GifImage{\" + \"url='\" + url + '\\'' + \", width=\" + width + \", height=\" + height + '}';\n    }\n  }\n}\n"
  },
  {
    "path": "samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/FullscreenActivity.java",
    "content": "package com.bumptech.glide.samples.giphy;\n\nimport android.app.Activity;\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.load.resource.gif.GifDrawable;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.Target;\nimport com.google.gson.Gson;\n\n/** An {@link android.app.Activity} for displaying full size original GIFs. */\npublic class FullscreenActivity extends Activity {\n  private static final String EXTRA_RESULT_JSON = \"result_json\";\n  private GifDrawable gifDrawable;\n\n  public static Intent getIntent(Context context, Api.GifResult result) {\n    Intent intent = new Intent(context, FullscreenActivity.class);\n    intent.putExtra(EXTRA_RESULT_JSON, new Gson().toJson(result));\n    return intent;\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.fullscreen_activity);\n\n    String resultJson = getIntent().getStringExtra(EXTRA_RESULT_JSON);\n    final Api.GifResult result = new Gson().fromJson(resultJson, Api.GifResult.class);\n\n    ImageView gifView = (ImageView) findViewById(R.id.fullscreen_gif);\n\n    gifView.setOnClickListener(\n        new View.OnClickListener() {\n          @Override\n          public void onClick(View view) {\n            ClipboardManager clipboard =\n                (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);\n            ClipData clip = ClipData.newPlainText(\"giphy_url\", result.images.original.url);\n            clipboard.setPrimaryClip(clip);\n\n            if (gifDrawable != null) {\n              if (gifDrawable.isRunning()) {\n                gifDrawable.stop();\n              } else {\n                gifDrawable.start();\n              }\n            }\n          }\n        });\n\n    RequestBuilder<Drawable> thumbnailRequest =\n        GlideApp.with(this).load(result).decode(Bitmap.class);\n\n    GlideApp.with(this)\n        .load(result.images.original.url)\n        .thumbnail(thumbnailRequest)\n        .listener(\n            new RequestListener<Drawable>() {\n              @Override\n              public boolean onLoadFailed(\n                  GlideException e,\n                  Object model,\n                  @NonNull Target<Drawable> target,\n                  boolean isFirstResource) {\n                return false;\n              }\n\n              @Override\n              public boolean onResourceReady(\n                  @NonNull Drawable resource,\n                  @NonNull Object model,\n                  Target<Drawable> target,\n                  @NonNull DataSource dataSource,\n                  boolean isFirstResource) {\n                if (resource instanceof GifDrawable) {\n                  gifDrawable = (GifDrawable) resource;\n                } else {\n                  gifDrawable = null;\n                }\n                return false;\n              }\n            })\n        .into(gifView);\n  }\n}\n"
  },
  {
    "path": "samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/GiphyGlideModule.java",
    "content": "package com.bumptech.glide.samples.giphy;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\nimport java.io.InputStream;\n\n/** Configures Glide for the Giphy sample app. */\n@GlideModule\npublic class GiphyGlideModule extends AppGlideModule {\n  @Override\n  public void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    registry.append(Api.GifResult.class, InputStream.class, new GiphyModelLoader.Factory());\n  }\n\n  // Disable manifest parsing to avoid adding similar modules twice.\n  @Override\n  public boolean isManifestParsingEnabled() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/GiphyModelLoader.java",
    "content": "package com.bumptech.glide.samples.giphy;\n\nimport android.text.TextUtils;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.load.model.stream.BaseGlideUrlLoader;\nimport com.bumptech.glide.samples.giphy.Api.GifResult;\nimport java.io.InputStream;\n\n/**\n * A model loader that translates a POJO mirroring a JSON object representing a single image from\n * Giphy's api into an {@link java.io.InputStream} that can be decoded into an {@link\n * android.graphics.drawable.Drawable}.\n */\npublic final class GiphyModelLoader extends BaseGlideUrlLoader<Api.GifResult> {\n\n  @Override\n  public boolean handles(@NonNull Api.GifResult model) {\n    return true;\n  }\n\n  private GiphyModelLoader(ModelLoader<GlideUrl, InputStream> urlLoader) {\n    super(urlLoader);\n  }\n\n  @Override\n  protected String getUrl(Api.GifResult model, int width, int height, Options options) {\n    Api.GifImage fixedHeight = model.images.fixed_height;\n    int fixedHeightDifference = getDifference(fixedHeight, width, height);\n    Api.GifImage fixedWidth = model.images.fixed_width;\n    int fixedWidthDifference = getDifference(fixedWidth, width, height);\n    if (fixedHeightDifference < fixedWidthDifference && !TextUtils.isEmpty(fixedHeight.url)) {\n      return fixedHeight.url;\n    } else if (!TextUtils.isEmpty(fixedWidth.url)) {\n      return fixedWidth.url;\n    } else if (!TextUtils.isEmpty(model.images.original.url)) {\n      return model.images.original.url;\n    } else {\n      return null;\n    }\n  }\n\n  private static int getDifference(Api.GifImage gifImage, int width, int height) {\n    return Math.abs(width - gifImage.width) + Math.abs(height - gifImage.height);\n  }\n\n  /** The default factory for {@link com.bumptech.glide.samples.giphy.GiphyModelLoader}s. */\n  public static final class Factory implements ModelLoaderFactory<GifResult, InputStream> {\n    @NonNull\n    @Override\n    public ModelLoader<Api.GifResult, InputStream> build(MultiModelLoaderFactory multiFactory) {\n      return new GiphyModelLoader(multiFactory.build(GlideUrl.class, InputStream.class));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n}\n"
  },
  {
    "path": "samples/giphy/src/main/java/com/bumptech/glide/samples/giphy/MainActivity.java",
    "content": "package com.bumptech.glide.samples.giphy;\n\nimport android.app.Activity;\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.recyclerview.widget.RecyclerView.RecyclerListener;\nimport androidx.recyclerview.widget.RecyclerView.ViewHolder;\nimport com.bumptech.glide.ListPreloader;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader;\nimport com.bumptech.glide.util.Preconditions;\nimport com.bumptech.glide.util.ViewPreloadSizeProvider;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * The primary activity in the Giphy sample that allows users to view trending animated GIFs from\n * Giphy's api.\n */\npublic class MainActivity extends Activity implements Api.Monitor {\n  private GifAdapter adapter;\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n\n    ImageView giphyLogoView = findViewById(R.id.giphy_logo_view);\n\n    GlideApp.with(this).load(R.raw.large_giphy_logo).into(giphyLogoView);\n\n    RecyclerView gifList = findViewById(R.id.gif_list);\n    LinearLayoutManager layoutManager = new LinearLayoutManager(this);\n    gifList.setLayoutManager(layoutManager);\n\n    RequestBuilder<Drawable> gifItemRequest = GlideApp.with(this).asDrawable();\n\n    ViewPreloadSizeProvider<Api.GifResult> preloadSizeProvider = new ViewPreloadSizeProvider<>();\n    adapter = new GifAdapter(this, gifItemRequest, preloadSizeProvider);\n    gifList.setAdapter(adapter);\n    RecyclerViewPreloader<Api.GifResult> preloader =\n        new RecyclerViewPreloader<>(GlideApp.with(this), adapter, preloadSizeProvider, 4);\n    gifList.addOnScrollListener(preloader);\n    gifList.addRecyclerListener(\n        new RecyclerListener() {\n          @Override\n          public void onViewRecycled(ViewHolder holder) {\n            // This is an optimization to reduce the memory usage of RecyclerView's recycled view\n            // pool\n            // and good practice when using Glide with RecyclerView.\n            GifViewHolder gifViewHolder = (GifViewHolder) holder;\n            GlideApp.with(MainActivity.this).clear(gifViewHolder.gifView);\n          }\n        });\n  }\n\n  @Override\n  protected void onStart() {\n    super.onStart();\n    Api.get().addMonitor(this);\n    if (adapter.getItemCount() == 0) {\n      Api.get().getTrending();\n    }\n  }\n\n  @Override\n  protected void onStop() {\n    super.onStop();\n    Api.get().removeMonitor(this);\n  }\n\n  @Override\n  public void onSearchComplete(Api.SearchResult result) {\n    adapter.setResults(result.data);\n  }\n\n  private static class GifAdapter extends RecyclerView.Adapter<GifViewHolder>\n      implements ListPreloader.PreloadModelProvider<Api.GifResult> {\n    private static final Api.GifResult[] EMPTY_RESULTS = new Api.GifResult[0];\n\n    private final Activity activity;\n    private final RequestBuilder<Drawable> requestBuilder;\n    private final ViewPreloadSizeProvider<Api.GifResult> preloadSizeProvider;\n\n    private Api.GifResult[] results = EMPTY_RESULTS;\n\n    GifAdapter(\n        Activity activity,\n        RequestBuilder<Drawable> requestBuilder,\n        ViewPreloadSizeProvider<Api.GifResult> preloadSizeProvider) {\n      this.activity = activity;\n      this.requestBuilder = requestBuilder;\n      this.preloadSizeProvider = preloadSizeProvider;\n    }\n\n    void setResults(Api.GifResult[] results) {\n      if (results != null) {\n        this.results = results;\n      } else {\n        this.results = EMPTY_RESULTS;\n      }\n      notifyDataSetChanged();\n    }\n\n    @Override\n    public GifViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n      View view = activity.getLayoutInflater().inflate(R.layout.gif_list_item, parent, false);\n      return new GifViewHolder(view);\n    }\n\n    @Override\n    public void onBindViewHolder(GifViewHolder holder, int position) {\n      final Api.GifResult result = results[position];\n      holder.gifView.setOnClickListener(\n          new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n              ClipboardManager clipboard =\n                  (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);\n              ClipData clip = ClipData.newPlainText(\"giphy_url\", result.images.fixed_height.url);\n              Preconditions.checkNotNull(clipboard).setPrimaryClip(clip);\n\n              Intent fullscreenIntent = FullscreenActivity.getIntent(activity, result);\n              activity.startActivity(fullscreenIntent);\n            }\n          });\n\n      // clearOnDetach let's us stop animating GifDrawables that RecyclerView hasn't yet recycled\n      // but that are currently off screen.\n      requestBuilder.load(result).into(holder.gifView).clearOnDetach();\n\n      preloadSizeProvider.setView(holder.gifView);\n    }\n\n    @Override\n    public long getItemId(int i) {\n      return 0;\n    }\n\n    @Override\n    public int getItemCount() {\n      return results.length;\n    }\n\n    @NonNull\n    @Override\n    public List<Api.GifResult> getPreloadItems(int position) {\n      return Collections.singletonList(results[position]);\n    }\n\n    @Nullable\n    @Override\n    public RequestBuilder<Drawable> getPreloadRequestBuilder(@NonNull Api.GifResult item) {\n      return requestBuilder.load(item);\n    }\n  }\n\n  private static class GifViewHolder extends RecyclerView.ViewHolder {\n    private final ImageView gifView;\n\n    GifViewHolder(View itemView) {\n      super(itemView);\n      gifView = itemView.findViewById(R.id.gif_view);\n    }\n  }\n}\n"
  },
  {
    "path": "samples/giphy/src/main/res/layout/activity_main.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:tools=\"http://schemas.android.com/tools\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:layout_margin=\"@dimen/activity_margin\"\n              android:orientation=\"vertical\"\n              tools:context=\".MainActivity\">\n    <ImageView\n        android:id=\"@+id/giphy_logo_view\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"50dp\"\n        android:layout_gravity=\"center_horizontal\"\n        android:contentDescription=\"@string/giphy_logo_description\" />\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/gif_list\"\n        android:layout_height=\"0dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_weight=\"1\"/>\n</LinearLayout>\n"
  },
  {
    "path": "samples/giphy/src/main/res/layout/fullscreen_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView\n   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n   android:id=\"@+id/fullscreen_gif\"\n   android:layout_width=\"match_parent\"\n   android:layout_height=\"match_parent\"\n   android:contentDescription=\"@string/fullscreen_description\"\n   />\n"
  },
  {
    "path": "samples/giphy/src/main/res/layout/gif_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n           android:id=\"@+id/gif_view\"\n           android:layout_width=\"match_parent\"\n           android:layout_height=\"125dp\"\n           android:contentDescription=\"@string/image_description\"/>\n"
  },
  {
    "path": "samples/giphy/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <dimen name=\"activity_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "samples/giphy/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"app_name\">GiphySample</string>\n  <string name=\"image_description\">An image from the list of results for a search query</string>\n  <string name=\"giphy_logo_description\">An animated version of the Giphy company logo</string>\n  <string name=\"fullscreen_description\">Full screen view of an image</string>\n</resources>\n"
  },
  {
    "path": "samples/giphy/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "samples/imgur/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "samples/imgur/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.samples.imgur\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n        \n        versionCode = 1\n        versionName = \"1.0\"\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n        }\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    annotationProcessor(project(\":annotation:compiler\"))\n\n    implementation(libs.dagger.runtime)\n    implementation(libs.dagger.android)\n    annotationProcessor(libs.dagger.compiler)\n    annotationProcessor(libs.dagger.android.processor)\n\n    implementation(libs.okhttp3)\n    implementation(libs.retrofit.runtime)\n    implementation(libs.retrofit.gson)\n    implementation(libs.retrofit.rxjava)\n\n    implementation(libs.rx.android)\n    implementation(libs.rx.java)\n\n    implementation(libs.androidx.appcompat)\n    implementation(libs.androidx.cardview)\n    implementation(libs.androidx.recyclerview)\n\n    // Fixes a compilation warning related to dagger, see\n    // https://github.com/google/guava/issues/2721.\n    compileOnly(libs.errorprone.annotations)\n}"
  },
  {
    "path": "samples/imgur/gradle.properties",
    "content": "android.useAndroidX=true\nandroid.enableJetifier=true"
  },
  {
    "path": "samples/imgur/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <!-- Not supported by all build systems -->\n    <issue id=\"GradleOverrides\" severity=\"ignore\" />\n    <issue id=\"IconMissingDensityFolder\" severity=\"ignore\"/>\n    <issue id=\"GoogleAppIndexingWarning\" severity=\"ignore\"/>\n    <issue id=\"GradleDependency\" severity=\"ignore\" />\n    <issue id=\"Autofill\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "samples/imgur/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\">\n  <uses-permission android:name=\"android.permission.INTERNET\" />\n  <!--\n  Allows Glide to monitor connectivity status and restart failed requests if users go from a\n  a disconnected to a connected network state.\n  -->\n  <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>\n  <application\n    android:allowBackup=\"false\"\n    android:icon=\"@mipmap/ic_launcher\"\n    android:label=\"@string/app_name\"\n    android:roundIcon=\"@mipmap/ic_launcher_round\"\n    android:supportsRtl=\"true\"\n    android:theme=\"@style/AppTheme\"\n    android:networkSecurityConfig=\"@xml/network_security_config\"\n    android:name=\"com.bumptech.glide.samples.imgur.ImgurApplication\"\n    tools:targetApi=\"n\">\n    <activity\n        android:exported=\"true\"\n        android:name=\"com.bumptech.glide.samples.imgur.MainActivity\">\n      <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\"/>\n        <category android:name=\"android.intent.category.LAUNCHER\"/>\n      </intent-filter>\n    </activity>\n  </application>\n\n</manifest>\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/ApplicationModule.java",
    "content": "package com.bumptech.glide.samples.imgur;\n\nimport dagger.Module;\nimport dagger.Provides;\nimport okhttp3.OkHttpClient;\n\n/** The Application Dagger module for the Imgur sample. */\n@Module\nclass ApplicationModule {\n  @Provides\n  OkHttpClient okHttpClient() {\n    return new OkHttpClient();\n  }\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/ImgurApplication.java",
    "content": "package com.bumptech.glide.samples.imgur;\n\nimport dagger.android.AndroidInjector;\nimport dagger.android.DaggerApplication;\n\n/** Runs Dagger injection in the Imgur sample. */\npublic final class ImgurApplication extends DaggerApplication {\n  @Override\n  protected AndroidInjector<? extends DaggerApplication> applicationInjector() {\n    return DaggerImgurApplicationComponent.create();\n  }\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/ImgurApplicationComponent.java",
    "content": "package com.bumptech.glide.samples.imgur;\n\nimport com.bumptech.glide.samples.imgur.api.ApiModule;\nimport dagger.Component;\nimport dagger.android.AndroidInjectionModule;\nimport dagger.android.AndroidInjector;\nimport javax.inject.Singleton;\n\n/** Specifies Dagger modules for {@link ImgurApplication}. */\n@Singleton\n@Component(\n    modules = {\n      AndroidInjectionModule.class,\n      MainActivityModule.class,\n      ApplicationModule.class,\n      ApiModule.class\n    })\npublic interface ImgurApplicationComponent extends AndroidInjector<ImgurApplication> {\n  // Empty.\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/ImgurGlideModule.java",
    "content": "package com.bumptech.glide.samples.imgur;\n\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\n\n/** Generates a Glide API for the Imgur sample. */\n@GlideModule(glideName = \"ImgurGlide\")\npublic class ImgurGlideModule extends AppGlideModule {\n  // Intentionally Empty.\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/MainActivity.java",
    "content": "package com.bumptech.glide.samples.imgur;\n\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.recyclerview.widget.RecyclerView.ViewHolder;\nimport com.bumptech.glide.samples.imgur.api.Image;\nimport dagger.android.AndroidInjection;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.inject.Inject;\nimport javax.inject.Named;\nimport rx.Observable;\nimport rx.Observer;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.schedulers.Schedulers;\n\n/** Displays images and GIFs from Imgur in a scrollable list of cards. */\npublic final class MainActivity extends AppCompatActivity {\n\n  @Inject\n  @Named(\"hotViralImages\")\n  Observable<List<Image>> fetchImagesObservable;\n\n  private ImgurImageAdapter adapter;\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    AndroidInjection.inject(this);\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);\n\n    recyclerView.setHasFixedSize(true);\n    LinearLayoutManager layoutManager = new LinearLayoutManager(this);\n    recyclerView.setLayoutManager(layoutManager);\n    adapter = new ImgurImageAdapter();\n    recyclerView.setAdapter(adapter);\n\n    fetchImagesObservable\n        .subscribeOn(Schedulers.newThread())\n        .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(\n            new Observer<List<Image>>() {\n              @Override\n              public void onCompleted() {}\n\n              @Override\n              public void onError(Throwable e) {}\n\n              @Override\n              public void onNext(List<Image> images) {\n                adapter.setData(images);\n              }\n            });\n  }\n\n  @Override\n  protected void onDestroy() {\n    super.onDestroy();\n    fetchImagesObservable.unsubscribeOn(AndroidSchedulers.mainThread());\n  }\n\n  private final class ImgurImageAdapter extends RecyclerView.Adapter<ViewHolder> {\n\n    private List<Image> images = Collections.emptyList();\n\n    public void setData(@NonNull List<Image> images) {\n      this.images = images;\n      notifyDataSetChanged();\n    }\n\n    @Override\n    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n      return new ViewHolder(\n          LayoutInflater.from(parent.getContext()).inflate(R.layout.image_card, parent, false));\n    }\n\n    @Override\n    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {\n      ViewHolder vh = (ViewHolder) holder;\n      Image image = images.get(position);\n      vh.title.setText(TextUtils.isEmpty(image.title) ? image.description : image.title);\n\n      ImgurGlide.with(vh.imageView).load(image.link).into(vh.imageView);\n    }\n\n    @Override\n    public int getItemCount() {\n      return images.size();\n    }\n\n    private final class ViewHolder extends RecyclerView.ViewHolder {\n\n      private final ImageView imageView;\n      private final TextView title;\n\n      ViewHolder(View itemView) {\n        super(itemView);\n        imageView = (ImageView) itemView.findViewById(R.id.image);\n        title = (TextView) itemView.findViewById(R.id.title);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/MainActivityModule.java",
    "content": "package com.bumptech.glide.samples.imgur;\n\nimport dagger.Module;\nimport dagger.android.ContributesAndroidInjector;\n\n@Module\nabstract class MainActivityModule {\n  @ContributesAndroidInjector\n  abstract MainActivity contributeMainActivityInjector();\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/api/ApiModule.java",
    "content": "package com.bumptech.glide.samples.imgur.api;\n\nimport dagger.Module;\nimport dagger.Provides;\nimport java.io.IOException;\nimport java.util.List;\nimport javax.inject.Named;\nimport javax.inject.Singleton;\nimport okhttp3.Interceptor;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Response;\nimport retrofit2.Retrofit;\nimport retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;\nimport retrofit2.converter.gson.GsonConverterFactory;\nimport rx.Observable;\n\n/** Provides classes related to the Imgur API via Dagger. */\n@Module\npublic final class ApiModule {\n\n  @Singleton\n  @Named(\"hotViralImages\")\n  @Provides\n  Observable<List<Image>> provideHotViralImages(ImgurObservables imgurObservables) {\n    return imgurObservables.getHotViralImages(5 /*maxPages*/);\n  }\n\n  @Provides\n  ImgurObservables imgurObservables(ImgurService imgurService) {\n    return new ImgurObservables(imgurService);\n  }\n\n  @Provides\n  ImgurService getImgurService(Retrofit retrofit) {\n    return retrofit.create(ImgurService.class);\n  }\n\n  @Provides\n  Retrofit retrofit() {\n    OkHttpClient client =\n        new OkHttpClient.Builder()\n            .addInterceptor(\n                new Interceptor() {\n                  @Override\n                  public Response intercept(Chain chain) throws IOException {\n                    return chain.proceed(\n                        chain\n                            .request()\n                            .newBuilder()\n                            .addHeader(\"Authorization\", \"Client-ID \" + ImgurService.CLIENT_ID)\n                            .build());\n                  }\n                })\n            .build();\n    return new Retrofit.Builder()\n        .client(client)\n        .addConverterFactory(GsonConverterFactory.create())\n        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())\n        .baseUrl(\"https://api.imgur.com/3/\")\n        .build();\n  }\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/api/Gallery.java",
    "content": "package com.bumptech.glide.samples.imgur.api;\n\nimport java.util.List;\n\n/**\n * Represents Imgur's Gallery resource.\n *\n * <p>Populated automatically by GSON.\n */\nfinal class Gallery {\n  public List<Image> data;\n\n  @Override\n  public String toString() {\n    return \"Gallery{\" + \"data=\" + data + '}';\n  }\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/api/Image.java",
    "content": "package com.bumptech.glide.samples.imgur.api;\n\n/**\n * Represents Imgur's Image resource.\n *\n * <p>Populated automatically by GSON\n */\npublic final class Image {\n  private String id;\n  public String title;\n  public String description;\n  public String link;\n  boolean is_album;\n\n  @Override\n  public String toString() {\n    return \"Image{\"\n        + \"id='\"\n        + id\n        + '\\''\n        + \", title='\"\n        + title\n        + '\\''\n        + \", description='\"\n        + description\n        + '\\''\n        + \", link='\"\n        + link\n        + '\\''\n        + \", is_album='\"\n        + is_album\n        + '\\''\n        + '}';\n  }\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/api/ImgurObservables.java",
    "content": "package com.bumptech.glide.samples.imgur.api;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport rx.Observable;\nimport rx.functions.Func1;\nimport rx.functions.Func2;\n\n/** Observables for retrieving metadata from Imgur's API. */\nfinal class ImgurObservables {\n\n  private final ImgurService imgurService;\n\n  ImgurObservables(ImgurService imgurService) {\n    this.imgurService = imgurService;\n  }\n\n  Observable<List<Image>> getHotViralImages(@SuppressWarnings(\"SameParameterValue\") int maxPages) {\n    return Observable.range(0, maxPages)\n        .flatMap(\n            new Func1<Integer, Observable<List<Image>>>() {\n              @Override\n              public Observable<List<Image>> call(Integer integer) {\n                return imgurService\n                    .getHotViral(integer)\n                    .map(new GetData())\n                    .flatMap(\n                        new Func1<List<Image>, Observable<List<Image>>>() {\n                          @Override\n                          public Observable<List<Image>> call(List<Image> images) {\n                            for (Iterator<Image> iterator = images.iterator();\n                                iterator.hasNext(); ) {\n                              if (iterator.next().is_album) {\n                                iterator.remove();\n                              }\n                            }\n                            return Observable.just(images);\n                          }\n                        });\n              }\n            })\n        .takeWhile(\n            new Func1<List<Image>, Boolean>() {\n              @Override\n              public Boolean call(List<Image> images) {\n                return !images.isEmpty();\n              }\n            })\n        .scan(\n            new Func2<List<Image>, List<Image>, List<Image>>() {\n              @Override\n              public List<Image> call(List<Image> images, List<Image> images2) {\n                List<Image> result = new ArrayList<>(images.size() + images2.size());\n                result.addAll(images);\n                result.addAll(images2);\n                return result;\n              }\n            })\n        .cache();\n  }\n\n  private static class GetData implements Func1<Gallery, List<Image>> {\n    @Override\n    public List<Image> call(Gallery gallery) {\n      return gallery.data;\n    }\n  }\n}\n"
  },
  {
    "path": "samples/imgur/src/main/java/com/bumptech/glide/samples/imgur/api/ImgurService.java",
    "content": "package com.bumptech.glide.samples.imgur.api;\n\nimport retrofit2.http.GET;\nimport retrofit2.http.Path;\nimport rx.Observable;\n\n/** Define's Imgur's API for Retrofit. */\npublic interface ImgurService {\n  String CLIENT_ID = \"36d1f6bef16370c\";\n\n  @GET(\"gallery/hot/viral/{page}\")\n  Observable<Gallery> getHotViral(@Path(\"page\") int page);\n\n  @GET(\"gallery/hot/{sort}/{page}.json\")\n  Observable<Gallery> getHot(@Path(\"sort\") Sort sort, @Path(\"page\") int page);\n\n  @GET(\"gallery/{section}/{sort}/{page}.json\")\n  Observable<Gallery> getGallery(\n      @Path(\"section\") Section section, @Path(\"sort\") Sort sort, @Path(\"page\") int page);\n\n  /** Sections that Imgur's API allows us to query from. */\n  enum Section {\n    hot,\n    top,\n    user\n  }\n\n  /** The sort order for content within a particular section. */\n  enum Sort {\n    viral,\n    top,\n    time,\n    rising\n  }\n}\n"
  },
  {
    "path": "samples/imgur/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  tools:context=\"com.bumptech.glide.samples.imgur.MainActivity\">\n\n  <androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/recycler_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:scrollbars=\"vertical\"\n    />\n</merge>\n"
  },
  {
    "path": "samples/imgur/src/main/res/layout/image_card.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:card_view=\"http://schemas.android.com/apk/res-auto\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"wrap_content\"\n  android:padding=\"16dp\">\n\n  <androidx.cardview.widget.CardView\n    android:id=\"@+id/card_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    card_view:cardCornerRadius=\"4dp\">\n    <LinearLayout\n      android:orientation=\"vertical\"\n      android:padding=\"16dp\"\n      android:gravity=\"center\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\">\n      <ImageView\n        android:id=\"@+id/image\"\n        android:scaleType=\"fitCenter\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"150dp\"\n        android:contentDescription=\"@null\"/>\n      <TextView\n        android:id=\"@+id/title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:paddingTop=\"16dp\"\n        />\n    </LinearLayout>\n  </androidx.cardview.widget.CardView>\n</FrameLayout>\n"
  },
  {
    "path": "samples/imgur/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": "samples/imgur/src/main/res/values/strings.xml",
    "content": "<resources>\n  <string name=\"app_name\">Imgur</string>\n</resources>\n"
  },
  {
    "path": "samples/imgur/src/main/res/values/styles.xml",
    "content": "<resources>\n\n  <!-- Base application theme. -->\n  <style name=\"AppTheme\" parent=\"Theme.AppCompat\">\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</resources>\n"
  },
  {
    "path": "samples/imgur/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\">imgur.com</domain>\n  </domain-config>\n</network-security-config>\n"
  },
  {
    "path": "samples/svg/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.samples.svg\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n        \n\n        versionCode = 1\n        versionName = \"1.0\"\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation(project(\":library\"))\n    annotationProcessor(project(\":annotation:compiler\"))\n    implementation(libs.svg)\n    implementation(libs.androidx.fragment)\n}"
  },
  {
    "path": "samples/svg/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <!-- Not supported by all build systems -->\n    <issue id=\"GradleOverrides\" severity=\"ignore\" />\n    <issue id=\"GoogleAppIndexingWarning\" severity=\"ignore\"/>\n    <issue id=\"GradleDependency\" severity=\"ignore\"/>\n    <issue id=\"Autofill\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "samples/svg/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n\n    <application\n        android:allowBackup=\"false\"\n        android:icon=\"@android:drawable/sym_def_app_icon\"\n        android:label=\"@string/app_name\">\n        <activity\n            android:exported=\"true\"\n            android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "samples/svg/src/main/java/com/bumptech/glide/samples/svg/MainActivity.java",
    "content": "package com.bumptech.glide.samples.svg;\n\nimport static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;\n\nimport android.app.Activity;\nimport android.content.ContentResolver;\nimport android.graphics.drawable.PictureDrawable;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.util.Preconditions;\nimport java.io.File;\n\n/** Displays an SVG image loaded from an android raw resource. */\npublic class MainActivity extends Activity {\n  private static final String TAG = \"SVGActivity\";\n\n  private ImageView imageViewRes;\n  private ImageView imageViewNet;\n  private RequestBuilder<PictureDrawable> requestBuilder;\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n\n    imageViewRes = (ImageView) findViewById(R.id.svg_image_view1);\n    imageViewNet = (ImageView) findViewById(R.id.svg_image_view2);\n\n    requestBuilder =\n        GlideApp.with(this)\n            .as(PictureDrawable.class)\n            .placeholder(R.drawable.image_loading)\n            .error(R.drawable.image_error)\n            .transition(withCrossFade())\n            .listener(new SvgSoftwareLayerSetter());\n  }\n\n  @Override\n  protected void onStart() {\n    super.onStart();\n    reload();\n  }\n\n  public void clearCache(View v) {\n    Log.w(TAG, \"clearing cache\");\n    GlideRequests glideRequests = GlideApp.with(this);\n    glideRequests.clear(imageViewRes);\n    glideRequests.clear(imageViewNet);\n    GlideApp.get(this).clearMemory();\n    File cacheDir = Preconditions.checkNotNull(Glide.getPhotoCacheDir(this));\n    if (cacheDir.isDirectory()) {\n      for (File child : cacheDir.listFiles()) {\n        if (!child.delete()) {\n          Log.w(TAG, \"cannot delete: \" + child);\n        }\n      }\n    }\n    reload();\n  }\n\n  public void cycleScaleType(View v) {\n    ImageView.ScaleType curr = imageViewRes.getScaleType();\n    Log.w(TAG, \"cycle: current=\" + curr);\n    ImageView.ScaleType[] all = ImageView.ScaleType.values();\n    int nextOrdinal = (curr.ordinal() + 1) % all.length;\n    ImageView.ScaleType next = all[nextOrdinal];\n    Log.w(TAG, \"cycle: next=\" + next);\n    imageViewRes.setScaleType(next);\n    imageViewNet.setScaleType(next);\n    reload();\n  }\n\n  private void reload() {\n    Log.w(TAG, \"reloading\");\n    ((TextView) findViewById(R.id.button))\n        .setText(getString(R.string.scaleType, imageViewRes.getScaleType()));\n    loadRes();\n    loadNet();\n  }\n\n  private void loadRes() {\n    Uri uri =\n        Uri.parse(\n            ContentResolver.SCHEME_ANDROID_RESOURCE\n                + \"://\"\n                + getPackageName()\n                + \"/\"\n                + R.raw.android_toy_h);\n    requestBuilder.load(uri).into(imageViewRes);\n  }\n\n  private void loadNet() {\n    Uri uri = Uri.parse(\"http://www.clker.com/cliparts/u/Z/2/b/a/6/android-toy-h.svg\");\n    requestBuilder.load(uri).into(imageViewNet);\n  }\n}\n"
  },
  {
    "path": "samples/svg/src/main/java/com/bumptech/glide/samples/svg/SvgDecoder.java",
    "content": "package com.bumptech.glide.samples.svg;\n\nimport static com.bumptech.glide.request.target.Target.SIZE_ORIGINAL;\n\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.ResourceDecoder;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.resource.SimpleResource;\nimport com.caverock.androidsvg.SVG;\nimport com.caverock.androidsvg.SVGParseException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/** Decodes an SVG internal representation from an {@link InputStream}. */\npublic class SvgDecoder implements ResourceDecoder<InputStream, SVG> {\n\n  @Override\n  public boolean handles(@NonNull InputStream source, @NonNull Options options) {\n    // TODO: Can we tell?\n    return true;\n  }\n\n  public Resource<SVG> decode(\n      @NonNull InputStream source, int width, int height, @NonNull Options options)\n      throws IOException {\n    try {\n      SVG svg = SVG.getFromInputStream(source);\n      if (width != SIZE_ORIGINAL) {\n        svg.setDocumentWidth(width);\n      }\n      if (height != SIZE_ORIGINAL) {\n        svg.setDocumentHeight(height);\n      }\n      return new SimpleResource<>(svg);\n    } catch (SVGParseException ex) {\n      throw new IOException(\"Cannot load SVG from stream\", ex);\n    }\n  }\n}\n"
  },
  {
    "path": "samples/svg/src/main/java/com/bumptech/glide/samples/svg/SvgDrawableTranscoder.java",
    "content": "package com.bumptech.glide.samples.svg;\n\nimport android.graphics.Picture;\nimport android.graphics.drawable.PictureDrawable;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.engine.Resource;\nimport com.bumptech.glide.load.resource.SimpleResource;\nimport com.bumptech.glide.load.resource.transcode.ResourceTranscoder;\nimport com.caverock.androidsvg.SVG;\n\n/**\n * Convert the {@link SVG}'s internal representation to an Android-compatible one ({@link Picture}).\n */\npublic class SvgDrawableTranscoder implements ResourceTranscoder<SVG, PictureDrawable> {\n  @Nullable\n  @Override\n  public Resource<PictureDrawable> transcode(\n      @NonNull Resource<SVG> toTranscode, @NonNull Options options) {\n    SVG svg = toTranscode.get();\n    Picture picture = svg.renderToPicture();\n    PictureDrawable drawable = new PictureDrawable(picture);\n    return new SimpleResource<>(drawable);\n  }\n}\n"
  },
  {
    "path": "samples/svg/src/main/java/com/bumptech/glide/samples/svg/SvgModule.java",
    "content": "package com.bumptech.glide.samples.svg;\n\nimport android.content.Context;\nimport android.graphics.drawable.PictureDrawable;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Registry;\nimport com.bumptech.glide.annotation.GlideModule;\nimport com.bumptech.glide.module.AppGlideModule;\nimport com.caverock.androidsvg.SVG;\nimport java.io.InputStream;\n\n/** Module for the SVG sample app. */\n@GlideModule\npublic class SvgModule extends AppGlideModule {\n  @Override\n  public void registerComponents(\n      @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {\n    registry\n        .register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder())\n        .append(InputStream.class, SVG.class, new SvgDecoder());\n  }\n\n  // Disable manifest parsing to avoid adding similar modules twice.\n  @Override\n  public boolean isManifestParsingEnabled() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "samples/svg/src/main/java/com/bumptech/glide/samples/svg/SvgSoftwareLayerSetter.java",
    "content": "package com.bumptech.glide.samples.svg;\n\nimport android.graphics.drawable.PictureDrawable;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.engine.GlideException;\nimport com.bumptech.glide.request.RequestListener;\nimport com.bumptech.glide.request.target.ImageViewTarget;\nimport com.bumptech.glide.request.target.Target;\n\n/**\n * Listener which updates the {@link ImageView} to be software rendered, because {@link\n * com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on a\n * hardware backed {@link android.graphics.Canvas Canvas}.\n */\npublic class SvgSoftwareLayerSetter implements RequestListener<PictureDrawable> {\n\n  @Override\n  public boolean onLoadFailed(\n      GlideException e,\n      Object model,\n      @NonNull Target<PictureDrawable> target,\n      boolean isFirstResource) {\n    ImageView view = ((ImageViewTarget<?>) target).getView();\n    view.setLayerType(ImageView.LAYER_TYPE_NONE, null);\n    return false;\n  }\n\n  @Override\n  public boolean onResourceReady(\n      @NonNull PictureDrawable resource,\n      @NonNull Object model,\n      Target<PictureDrawable> target,\n      @NonNull DataSource dataSource,\n      boolean isFirstResource) {\n    ImageView view = ((ImageViewTarget<?>) target).getView();\n    view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);\n    return false;\n  }\n}\n"
  },
  {
    "path": "samples/svg/src/main/res/drawable/dot_dot_dot.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- 10(gap) 10(first) 10(gap) 10(second) 10(gap) 10(third) 10(gap) -->\n    <item\n        android:left=\"10dp\"\n        android:right=\"50dp\">\n        <shape android:shape=\"oval\">\n            <size\n                android:width=\"10dp\"\n                android:height=\"10dp\"/>\n            <solid android:color=\"@android:color/black\"/>\n        </shape>\n    </item>\n    <item\n        android:left=\"30dp\"\n        android:right=\"30dp\">\n        <shape android:shape=\"oval\">\n            <size\n                android:width=\"10dp\"\n                android:height=\"10dp\"/>\n            <solid android:color=\"@android:color/black\"/>\n        </shape>\n    </item>\n    <item\n        android:left=\"50dp\"\n        android:right=\"10dp\">\n        <shape android:shape=\"oval\">\n            <size\n                android:width=\"10dp\"\n                android:height=\"10dp\"/>\n            <solid android:color=\"@android:color/black\"/>\n        </shape>\n    </item>\n</layer-list>\n"
  },
  {
    "path": "samples/svg/src/main/res/drawable/image_error.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- top-left to bottom-right line -->\n    <item>\n        <rotate android:fromDegrees=\"45\">\n            <shape android:shape=\"line\">\n                <size\n                    android:width=\"128dp\"\n                    android:height=\"128dp\"/>\n                <stroke\n                    android:color=\"#f00\"\n                    android:width=\"4dp\"/>\n            </shape>\n        </rotate>\n    </item>\n\n    <!-- top-right to bottom-left line -->\n    <item>\n        <rotate android:fromDegrees=\"-45\">\n            <shape android:shape=\"line\">\n                <size\n                    android:width=\"128dp\"\n                    android:height=\"128dp\"/>\n                <stroke\n                    android:color=\"#f00\"\n                    android:width=\"4dp\"/>\n            </shape>\n        </rotate>\n    </item>\n</layer-list>\n"
  },
  {
    "path": "samples/svg/src/main/res/drawable/image_loading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <bitmap\n            android:src=\"@mipmap/ic_launcher\"\n            android:gravity=\"center\" />\n    </item>\n    <item\n        android:top=\"20dp\"\n        android:bottom=\"20dp\"\n        android:drawable=\"@drawable/dot_dot_dot\"/>\n</layer-list>\n"
  },
  {
    "path": "samples/svg/src/main/res/layout/activity_main.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:tools=\"http://schemas.android.com/tools\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:paddingBottom=\"@dimen/activity_vertical_margin\"\n                android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n                android:paddingRight=\"@dimen/activity_horizontal_margin\"\n                android:paddingTop=\"@dimen/activity_vertical_margin\"\n                tools:context=\".MainActivity\">\n\n    <TextView\n        android:id=\"@+id/description\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/activity_vertical_margin\"\n        android:onClick=\"clearCache\"\n        android:clickable=\"true\"\n        android:text=\"@string/hello_world\"\n        android:focusable=\"true\"/>\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"@dimen/activity_vertical_margin\"\n        android:onClick=\"cycleScaleType\"\n        android:text=\"@string/scaleType\"\n        />\n\n    <View\n        android:id=\"@+id/align\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        />\n\n    <ImageView\n        android:id=\"@+id/svg_image_view1\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_below=\"@id/description\"\n        android:layout_above=\"@id/button\"\n        android:layout_toLeftOf=\"@id/align\"\n        android:layout_toStartOf=\"@id/align\"\n        android:layout_centerVertical=\"true\"\n        android:background=\"#808\"\n        android:contentDescription=\"@string/android_linen_content_description\"\n        android:src=\"@drawable/image_error\"\n        />\n\n    <ImageView\n        android:id=\"@+id/svg_image_view2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_below=\"@id/description\"\n        android:layout_above=\"@id/button\"\n        android:layout_toRightOf=\"@id/align\"\n        android:layout_toEndOf=\"@id/align\"\n        android:layout_centerVertical=\"true\"\n        android:background=\"#088\"\n        android:contentDescription=\"@string/android_linen_content_description\"\n        android:src=\"@drawable/image_error\"\n        />\n\n</RelativeLayout>\n"
  },
  {
    "path": "samples/svg/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "samples/svg/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"app_name\">SVGSample</string>\n    <string name=\"hello_world\" tools:ignore=\"Typos\">An Android robot playing with a blue linen should appear below after a \\\"dot dot dot\\\" loading image on both the left and the right side. The left is loaded from a raw resource and the right is from the internet. Click here to clear all caches.</string>\n    <string name=\"scaleType\">Click to cycle scale type (current: %1$s)</string>\n    <string name=\"android_linen_content_description\">An android playing with blue linen</string>\n</resources>\n"
  },
  {
    "path": "samples/svg/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "scripts/run_instrumentation_tests.sh",
    "content": "#!/usr/bin/env bash\n# Runs instrumentation tests on firebase. Must be run locally, not on travis.\n#\n# Usage: \n# ./scripts/run_instrumentation_test.sh\n\n./gradlew :instrumentation:assembleDebug :instrumentation:assembleDebugAndroidTest --parallel\n\napk_dir=instrumentation/build/outputs/apk\ngcloud firebase test android run \\\n  --type instrumentation \\\n  --app $apk_dir/instrumentation-debug.apk \\\n  --test $apk_dir/instrumentation-debug-androidTest.apk \\\n  --device model=Nexus6P,version=26,locale=en,orientation=portrait  \\\n  --device model=Nexus6P,version=25,locale=en,orientation=portrait \\\n  --device model=Nexus6P,version=23,locale=en,orientation=portrait \\\n  --device model=Nexus6,version=22,locale=en,orientation=portrait \\\n  --device model=Nexus5,version=19,locale=en,orientation=portrait \\\n  --device model=MediumPhone.arm,version=34,locale=en,orientation=portrait \\\n  --project android-glide \\\n  --no-auto-google-login \\\n"
  },
  {
    "path": "scripts/update_javadocs.sh",
    "content": "#!/bin/bash\n#\n# Usage: ./scripts/update_javadocs.sh\n#\n# The version name is pulled automatically from gradle.properties.\nset -e\nset -o pipefail\n\nTEMP_DIR=\"/tmp/tmp_glide_javadoc\"\nJAVADOC_GH_PAGES_DIR=\"javadocs\"\n\nmajor_version=$(grep -F VERSION_MAJOR gradle.properties | cut -d '=' -f 2)\nminor_version=$(grep -F VERSION_MINOR gradle.properties | cut -d '=' -f 2)\nversion=\"${major_version}${minor_version}0\"\n\necho \"Updating javadocs for ${version}\"\n\nif [[ $(git status -uno --porcelain) ]];\nthen\n  echo \"One or more changes, commit or revert first.\"\n  git status -uno --porcelain\n  exit 1\nfi\n\nif [ -e \"$JAVADOC_GH_PAGES_DIR\" ];\nthen\n  echo \"javadocs directory exists locally, remove first.\"\n  exit 1\nfi\n\nif [[ $(git rev-list master...origin/master --count) -ne 0 ]];\nthen\n  echo \"Origin and master are not up to date\"\n  git rev-list master...origin/master --pretty\n  exit 1\nfi\nif [[ $(git rev-list gh-pages...origin/gh-pages --count) -ne 0 ]];\nthen\n  echo \"Origin and gh-pages are not up to date\"\n  git rev-list gh-pages...origin/gh-pages --pretty\n  exit 1\nfi\n\ngit checkout master\nGIT_COMMIT_SHA=\"$(git rev-parse HEAD)\"\n./gradlew :dokkaHtmlMultiModule\nrm -rf $TEMP_DIR\ncp -r build/dokka/htmlMultiModule $TEMP_DIR\n\ngit checkout gh-pages\nrm -rf \"${JAVADOC_GH_PAGES_DIR}/${version}\"\ncp -r $TEMP_DIR $JAVADOC_GH_PAGES_DIR/$version\nrm -rf $TEMP_DIR\ngit add \"${JAVADOC_GH_PAGES_DIR}/$version\"\ngit commit -m \"Update javadocs for version $version\" -m \"Generated from commit on master branch: ${GIT_COMMIT_SHA}\"\necho \"Copied javadoc into ${JAVADOC_GH_PAGES_DIR}/${version} and committed\"\ngit log -1 --pretty=%B\necho \"Ready to push\"\n"
  },
  {
    "path": "scripts/upload.gradle.kts",
    "content": "import org.gradle.kotlin.dsl.configure\nimport org.gradle.kotlin.dsl.withGroovyBuilder\n\npluginManager.apply(\"com.vanniktech.maven.publish\")\n\n// Helper function to get project properties\nfun prop(key: String): String? = if (hasProperty(key)) property(key)?.toString() else null\n\n// Grab everything we will need for publishing\nval groupId: String = prop(\"GROUP\") ?: group.toString()\nval artifactId: String = prop(\"POM_ARTIFACT_ID\") ?: name\nval versionName: String = prop(\"VERSION_NAME\") ?: version.toString()\n\nif (pluginManager.hasPlugin(\"java\") || pluginManager.hasPlugin(\"java-library\")) {\n  extensions.configure(org.gradle.api.plugins.JavaPluginExtension::class.java) {\n    withSourcesJar()\n    // TODO(b/409147894): Put this back after explicit Dokka task dependency\n    // withJavadocJar()\n  }\n}\n\n// Sign and fill out POM\nextensions.findByName(\"mavenPublishing\")?.withGroovyBuilder {\n  \"signAllPublications\"()\n\n  \"pom\" {\n    \"licenses\" {\n      \"license\" {\n        setProperty(\"name\", \"Simplified BSD License\")\n        setProperty(\"url\", \"http://www.opensource.org/licenses/bsd-license\")\n        setProperty(\"distribution\", \"repo\")\n      }\n      \"license\" {\n        setProperty(\"name\", \"Apache License, Version 2.0\")\n        setProperty(\"url\", \"http://www.apache.org/licenses/LICENSE-2.0.txt\")\n        setProperty(\"distribution\", \"repo\")\n      }\n    }\n\n    \"developers\" {\n      \"developer\" {\n        setProperty(\"id\", prop(\"POM_DEVELOPER_ID\"))\n        setProperty(\"name\", prop(\"POM_DEVELOPER_NAME\"))\n        setProperty(\"email\", prop(\"POM_DEVELOPER_EMAIL\"))\n      }\n    }\n\n    \"scm\" {\n      setProperty(\"connection\", prop(\"POM_SCM_CONNECTION\"))\n      setProperty(\"developerConnection\", prop(\"POM_SCM_DEV_CONNECTION\"))\n      setProperty(\"url\", prop(\"POM_SCM_URL\"))\n    }\n  }\n}\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "import org.gradle.api.initialization.resolve.RepositoriesMode\n\npluginManagement {\n  repositories {\n    gradlePluginPortal()\n    google()\n    mavenCentral()\n    maven(url = \"https://oss.sonatype.org/content/repositories/snapshots\")\n  }\n}\n\ndependencyResolutionManagement {\n  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n  repositories {\n    google()\n    mavenCentral()\n  }\n}\n\nrootProject.name = \"glide-parent\"\n\ninclude(\":library\")\n\ninclude(\":library:pmd\")\n\ninclude(\":library:test\")\n\ninclude(\":instrumentation\")\n\ninclude(\":annotation\")\n\ninclude(\":annotation:compiler\")\n\n// include(\":annotation:compiler:test\")\ninclude(\":annotation:ksp\")\n\ninclude(\":annotation:ksp:test\")\n\ninclude(\":annotation:ksp:integrationtest\")\n\ninclude(\":benchmark\")\n\ninclude(\":glide\")\n\ninclude(\":third_party:gif_decoder\")\n\ninclude(\":third_party:disklrucache\")\n\ninclude(\":samples:flickr\")\n\ninclude(\":samples:giphy\")\n\ninclude(\":samples:svg\")\n\ninclude(\":samples:gallery\")\n\ninclude(\":samples:contacturi\")\n\ninclude(\":samples:imgur\")\n\ninclude(\":integration\")\n\ninclude(\":integration:avif\")\n\ninclude(\":integration:compose\")\n\ninclude(\":integration:concurrent\")\n\ninclude(\":integration:cronet\")\n\ninclude(\":integration:gifencoder\")\n\ninclude(\":integration:ktx\")\n\ninclude(\":integration:okhttp\")\n\ninclude(\":integration:okhttp3\")\n\ninclude(\":integration:okhttp4\")\n\ninclude(\":integration:recyclerview\")\n\ninclude(\":integration:sqljournaldiskcache\")\n\ninclude(\":integration:volley\")\n\ninclude(\":testutil\")\n\ninclude(\":mocks\")\n"
  },
  {
    "path": "static/logo-styles.css",
    "content": ".library-name a {\n    position: relative;\n    --logo-width: 75px;\n    margin-left: calc(var(--logo-width) + 5px);\n}\n\n.library-name a::before {\n    content: '';\n    background: url(\"../images/glide_circle_logo.png\") center no-repeat;\n    background-size: contain;\n    position: absolute;\n    width: var(--logo-width);\n    height: 50px;\n    top: -18px;\n    left: calc(-1 * var(--logo-width) - 5px);\n    /* other styles required to make your page pretty */\n}"
  },
  {
    "path": "testutil/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.testutil\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n}\n\ndependencies {\n    implementation(libs.truth)\n    implementation(project(\":library\"))\n    api(libs.androidx.annotation)\n    api(libs.androidx.core)\n    api(libs.androidx.test.core)\n}"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/RobolectricConstants.java",
    "content": "package com.bumptech.glide;\n\npublic class RobolectricConstants {\n  /** The default SDK used for Robolectric tests */\n  public static final int ROBOLECTRIC_SDK = 23;\n}\n"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/testutil/BitmapSubject.java",
    "content": "package com.bumptech.glide.testutil;\n\nimport static com.google.common.truth.Fact.simpleFact;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.core.content.res.ResourcesCompat;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.google.common.truth.FailureMetadata;\nimport com.google.common.truth.Subject;\nimport com.google.common.truth.Truth;\n\n/** Truth assertions for comparing {@link Bitmap}s. */\n@SuppressWarnings({\"WeakerAccess\", \"unused\", \"rawtypes\", \"unchecked\"})\npublic final class BitmapSubject extends Subject {\n\n  private static final Subject.Factory<BitmapSubject, Bitmap> FACTORY =\n      new Subject.Factory<BitmapSubject, Bitmap>() {\n        @Override\n        public BitmapSubject createSubject(\n            @NonNull FailureMetadata metadata, @NonNull Bitmap actual) {\n          return new BitmapSubject(metadata, actual);\n        }\n      };\n\n  private final Bitmap actual;\n\n  private BitmapSubject(FailureMetadata failureMetadata, Bitmap subject) {\n    super(failureMetadata, subject);\n    this.actual = subject;\n  }\n\n  public static BitmapSubject assertThat(Drawable drawable) {\n    if (!(drawable instanceof BitmapDrawable)) {\n      throw new IllegalArgumentException(\"Not a BitmapDrawable: \" + drawable);\n    }\n    return assertThat(((BitmapDrawable) drawable).getBitmap());\n  }\n\n  public static BitmapSubject assertThat(Bitmap bitmap) {\n    return Truth.assertAbout(FACTORY).that(bitmap);\n  }\n\n  @Override\n  protected String actualCustomStringRepresentation() {\n    return getDisplayString(actual);\n  }\n\n  private static String getDisplayString(Bitmap bitmap) {\n    return \"<\"\n        + \"[\"\n        + bitmap.getWidth()\n        + \"x\"\n        + bitmap.getHeight()\n        + \"]\"\n        + \" \"\n        + bitmap.getConfig()\n        + \">\";\n  }\n\n  public void sameAs(@DrawableRes int resourceId) {\n    Context context = ApplicationProvider.getApplicationContext();\n    Drawable drawable =\n        ResourcesCompat.getDrawable(context.getResources(), resourceId, context.getTheme());\n    sameAs(drawable);\n  }\n\n  public void hasDimensions(int expectedWidth, int expectedHeight) {\n    int actualWidth = actual.getWidth();\n    int actualHeight = actual.getHeight();\n    if (expectedWidth != actualWidth && expectedHeight != actualHeight) {\n      failWithActual(\"expected to have dimensions\", expectedWidth + \"x\" + expectedHeight);\n    } else if (expectedWidth != actualWidth) {\n      failWithActual(\"expected to have width\", expectedWidth);\n    } else if (expectedHeight != actualHeight) {\n      failWithActual(\"expected to have height\", expectedHeight);\n    }\n  }\n\n  public void isMutable() {\n    if (!actual.isMutable()) {\n      failWithActual(simpleFact(\"expected to be mutable\"));\n    }\n  }\n\n  public void isImmutable() {\n    if (actual.isMutable()) {\n      failWithActual(simpleFact(\"expected to be immutable\"));\n    }\n  }\n\n  public void isNotRecycled() {\n    if (actual.isRecycled()) {\n      failWithActual(simpleFact(\"expected not to be recycled\"));\n    }\n  }\n\n  public void sameAs(Drawable other) {\n    if (!(other instanceof BitmapDrawable)) {\n      failWithoutActual(simpleFact(\"The given expected value was not a BitmapDrawable.\"));\n    }\n    sameAs(((BitmapDrawable) other).getBitmap());\n  }\n\n  public void sameAs(Bitmap other) {\n    if (!actual.sameAs(other)) {\n      failWithActual(\"expected to be the same as\", getDisplayString(other));\n    }\n  }\n\n  public void isNotSameAs(Bitmap other) {\n    if (actual.sameAs(other)) {\n      failWithActual(\"expected not to be the same as\", getDisplayString(other));\n    }\n  }\n}\n"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/testutil/ConcurrencyHelper.java",
    "content": "package com.bumptech.glide.testutil;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.os.Debug;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.widget.ImageView;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestBuilder;\nimport com.bumptech.glide.request.FutureTarget;\nimport com.bumptech.glide.request.Request;\nimport com.bumptech.glide.request.target.DrawableImageViewTarget;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.target.Target;\nimport com.bumptech.glide.request.transition.Transition;\nimport com.bumptech.glide.util.Executors;\nimport com.bumptech.glide.util.Preconditions;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/** Helper for running sections of code on the main thread in emulator tests. */\npublic class ConcurrencyHelper {\n  private final Handler handler = new Handler(Looper.getMainLooper());\n  private static final long TIMEOUT_SECONDS = 10;\n  private static final TimeUnit TIMEOUT_UNIT = TimeUnit.SECONDS;\n\n  public <T> T get(final Future<T> future) {\n    final AtomicReference<T> reference = new AtomicReference<>();\n    wait(\n        new Waiter() {\n          @Override\n          public boolean await(long timeout, TimeUnit timeUnit) throws InterruptedException {\n            try {\n              reference.set(future.get(timeout, timeUnit));\n              return true;\n            } catch (ExecutionException e) {\n              throw new RuntimeException(e.getCause());\n            } catch (TimeoutException e) {\n              return false;\n            }\n          }\n        });\n    return reference.get();\n  }\n\n  public <T> Target<T> wait(FutureTarget<T> future) {\n    get(future);\n    return future;\n  }\n\n  public void loadOnOtherThread(final Runnable runnable) {\n    final AtomicBoolean isDone = new AtomicBoolean();\n    final Thread thread =\n        new Thread(\n            new Runnable() {\n              @Override\n              public void run() {\n                runnable.run();\n                isDone.set(true);\n              }\n            });\n    thread.start();\n\n    wait(\n        new Waiter() {\n          @Override\n          public boolean await(long timeout, TimeUnit timeUnit) throws InterruptedException {\n            thread.join(timeUnit.toMillis(timeout));\n            return isDone.get();\n          }\n        });\n  }\n\n  public void loadOnMainThread(final RequestBuilder<Drawable> builder, ImageView imageView) {\n    loadOnMainThread(builder, new DrawableImageViewTarget(imageView));\n  }\n\n  public void clearOnMainThread(final ImageView imageView) {\n    runOnMainThread(\n        new Runnable() {\n          @Override\n          // Required to avoid a weird emulator issue where the Application passed here is otherwise\n          // cast to a FragmentActivity...\n          @SuppressWarnings(\"cast\")\n          public void run() {\n            Glide.with((Context) ApplicationProvider.getApplicationContext()).clear(imageView);\n          }\n        });\n  }\n\n  public void loadUntilFirstFinish(final RequestBuilder<Drawable> builder, ImageView imageView) {\n    loadUntilFirstFinish(builder, new DrawableImageViewTarget(imageView));\n  }\n\n  private <T> void loadUntilFirstFinish(final RequestBuilder<T> builder, final Target<T> target) {\n    final CountDownLatch latch = new CountDownLatch(1);\n    callOnMainThread(\n        new Callable<Target<T>>() {\n          @Override\n          public Target<T> call() {\n            builder.into(\n                new Target<T>() {\n                  @Override\n                  public void onStart() {\n                    target.onStart();\n                  }\n\n                  @Override\n                  public void onStop() {\n                    target.onStop();\n                  }\n\n                  @Override\n                  public void onDestroy() {\n                    target.onDestroy();\n                  }\n\n                  @Override\n                  public void onResourceReady(\n                      @NonNull T resource, @Nullable Transition<? super T> transition) {\n                    target.onResourceReady(resource, transition);\n                    latch.countDown();\n                  }\n\n                  @Override\n                  public void onLoadCleared(@Nullable Drawable placeholder) {\n                    target.onLoadCleared(placeholder);\n                  }\n\n                  @Override\n                  public void onLoadStarted(@Nullable Drawable placeholder) {\n                    target.onLoadStarted(placeholder);\n                  }\n\n                  @Override\n                  public void onLoadFailed(@Nullable Drawable errorDrawable) {\n                    target.onLoadFailed(errorDrawable);\n                    latch.countDown();\n                  }\n\n                  @Override\n                  public void getSize(@NonNull SizeReadyCallback cb) {\n                    target.getSize(cb);\n                  }\n\n                  @Override\n                  public void removeCallback(@NonNull SizeReadyCallback cb) {\n                    target.removeCallback(cb);\n                  }\n\n                  @Override\n                  public void setRequest(@Nullable Request request) {\n                    target.setRequest(request);\n                  }\n\n                  @Nullable\n                  @Override\n                  public Request getRequest() {\n                    return target.getRequest();\n                  }\n                });\n            return target;\n          }\n        });\n    waitOnLatch(latch);\n  }\n\n  private <T> void loadOnMainThread(final RequestBuilder<T> builder, final Target<T> target) {\n    final CountDownLatch latch = new CountDownLatch(1);\n    callOnMainThread(\n        new Callable<Target<T>>() {\n          @Override\n          public Target<T> call() {\n            builder.into(\n                new Target<T>() {\n                  @Override\n                  public void onStart() {\n                    target.onStart();\n                  }\n\n                  @Override\n                  public void onStop() {\n                    target.onStop();\n                  }\n\n                  @Override\n                  public void onDestroy() {\n                    target.onDestroy();\n                  }\n\n                  @Override\n                  public void onResourceReady(\n                      @NonNull T resource, @Nullable Transition<? super T> transition) {\n                    target.onResourceReady(resource, transition);\n                    checkRequestAndMaybeReleaseLatch();\n                  }\n\n                  @Override\n                  public void onLoadCleared(@Nullable Drawable placeholder) {\n                    target.onLoadCleared(placeholder);\n                  }\n\n                  @Override\n                  public void onLoadStarted(@Nullable Drawable placeholder) {\n                    target.onLoadStarted(placeholder);\n                  }\n\n                  @Override\n                  public void onLoadFailed(@Nullable Drawable errorDrawable) {\n                    target.onLoadFailed(errorDrawable);\n                    checkRequestAndMaybeReleaseLatch();\n                  }\n\n                  @Override\n                  public void getSize(@NonNull SizeReadyCallback cb) {\n                    target.getSize(cb);\n                  }\n\n                  @Override\n                  public void removeCallback(@NonNull SizeReadyCallback cb) {\n                    target.removeCallback(cb);\n                  }\n\n                  @Override\n                  public void setRequest(@Nullable Request request) {\n                    target.setRequest(request);\n                  }\n\n                  @Nullable\n                  @Override\n                  public Request getRequest() {\n                    return target.getRequest();\n                  }\n\n                  // We can't guarantee the ordering of when this callback is called and when the\n                  // request's state is updated, so it's safer to post the check back to the UI\n                  // thread.\n                  private void checkRequestAndMaybeReleaseLatch() {\n                    Executors.mainThreadExecutor()\n                        .execute(\n                            new Runnable() {\n                              @Override\n                              public void run() {\n                                if (!Preconditions.checkNotNull(getRequest()).isRunning()) {\n                                  latch.countDown();\n                                }\n                              }\n                            });\n                  }\n                });\n            return target;\n          }\n        });\n    waitOnLatch(latch);\n  }\n\n  public void pokeMainThread() {\n    runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            // Do nothing.\n          }\n        });\n  }\n\n  public void runOnMainThread(final Runnable runnable) {\n    callOnMainThread(\n        new Callable<Void>() {\n          @Override\n          public Void call() {\n            runnable.run();\n            return null;\n          }\n        });\n  }\n\n  private <T> void callOnMainThread(final Callable<T> callable) {\n    final CountDownLatch latch = new CountDownLatch(1);\n    handler.post(\n        new Runnable() {\n          @Override\n          public void run() {\n            try {\n              callable.call();\n            } catch (Exception e) {\n              throw new RuntimeException(e);\n            }\n            latch.countDown();\n          }\n        });\n    waitOnLatch(latch);\n  }\n\n  public static void waitOnLatch(final CountDownLatch latch) {\n    wait(\n        new Waiter() {\n          @Override\n          public boolean await(long timeout, TimeUnit timeUnit) throws InterruptedException {\n            return latch.await(timeout, timeUnit);\n          }\n        });\n  }\n\n  private interface Waiter {\n    boolean await(long timeout, TimeUnit timeUnit) throws InterruptedException;\n  }\n\n  private static void wait(Waiter waiter) {\n    boolean isFinished = false;\n    do {\n      try {\n        try {\n          isFinished = waiter.await(TIMEOUT_SECONDS, TIMEOUT_UNIT);\n          if (!isFinished) {\n            throw new WaiterException(\"Timed out while waiting\");\n          }\n        } catch (InterruptedException e) {\n          throw new WaiterException(e);\n        }\n      } catch (WaiterException e) {\n        if (Debug.isDebuggerConnected()) {\n          continue;\n        }\n        throw e;\n      }\n    } while (Debug.isDebuggerConnected() && !isFinished);\n  }\n\n  private static final class WaiterException extends RuntimeException {\n    private static final long serialVersionUID = -627297254223169728L;\n\n    WaiterException(String message) {\n      super(message);\n    }\n\n    WaiterException(Throwable cause) {\n      super(cause);\n    }\n  }\n}\n"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/testutil/MockModelLoader.java",
    "content": "package com.bumptech.glide.testutil;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.data.DataFetcher.DataCallback;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.signature.ObjectKey;\nimport com.google.common.util.concurrent.FutureCallback;\nimport com.google.common.util.concurrent.Futures;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.common.util.concurrent.MoreExecutors;\n\npublic final class MockModelLoader<ModelT, DataT> implements ModelLoader<ModelT, DataT> {\n  private final ModelT model;\n  private final Class<DataT> dataClass;\n  private final ListenableFuture<DataT> dataFuture;\n\n  @SuppressWarnings(\"unchecked\")\n  public static <ModelT, DataT> void mock(final ModelT model, final DataT data) {\n    mockAsync(model, (Class<DataT>) data.getClass(), Futures.immediateFuture(data));\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public static <ModelT, DataT> void mockAsync(\n      final ModelT model, final Class<DataT> dataClass, final ListenableFuture<DataT> dataFuture) {\n    Context context = ApplicationProvider.getApplicationContext();\n\n    Glide.get(context)\n        .getRegistry()\n        .replace(\n            (Class<ModelT>) model.getClass(),\n            dataClass,\n            new ModelLoaderFactory<ModelT, DataT>() {\n              @NonNull\n              @Override\n              public ModelLoader<ModelT, DataT> build(\n                  @NonNull MultiModelLoaderFactory multiFactory) {\n                return new MockModelLoader<>(model, dataClass, dataFuture);\n              }\n\n              @Override\n              public void teardown() {\n                // Do nothing.\n              }\n            });\n  }\n\n  private MockModelLoader(\n      ModelT model, Class<DataT> dataClass, ListenableFuture<DataT> dataFuture) {\n    this.model = model;\n    this.dataClass = dataClass;\n    this.dataFuture = dataFuture;\n  }\n\n  @Override\n  public LoadData<DataT> buildLoadData(\n      @NonNull ModelT modelT, int width, int height, @NonNull Options options) {\n    return new LoadData<>(new ObjectKey(modelT), new MockDataFetcher<>(dataClass, dataFuture));\n  }\n\n  @Override\n  public boolean handles(@NonNull ModelT model) {\n    return this.model.equals(model);\n  }\n\n  private static final class MockDataFetcher<DataT> implements DataFetcher<DataT> {\n\n    private final ListenableFuture<DataT> dataFuture;\n    private final Class<DataT> dataClass;\n\n    MockDataFetcher(Class<DataT> dataClass, ListenableFuture<DataT> dataFuture) {\n      this.dataClass = dataClass;\n      this.dataFuture = dataFuture;\n    }\n\n    @Override\n    public void loadData(\n        @NonNull Priority priority, final @NonNull DataCallback<? super DataT> callback) {\n      Futures.addCallback(\n          dataFuture,\n          new FutureCallback<DataT>() {\n            @Override\n            public void onSuccess(DataT data) {\n              callback.onDataReady(data);\n            }\n\n            @Override\n            public void onFailure(Throwable t) {\n              if (t instanceof Exception) {\n                callback.onLoadFailed((Exception) t);\n              } else {\n                callback.onLoadFailed(new Exception(t));\n              }\n            }\n          },\n          MoreExecutors.directExecutor());\n    }\n\n    @Override\n    public void cleanup() {\n      // Do nothing.\n    }\n\n    @Override\n    public void cancel() {\n      dataFuture.cancel(true);\n    }\n\n    @NonNull\n    @Override\n    public Class<DataT> getDataClass() {\n      return dataClass;\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return DataSource.REMOTE;\n    }\n  }\n}\n"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/testutil/TearDownGlide.java",
    "content": "package com.bumptech.glide.testutil;\n\nimport android.content.Context;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.RequestManager;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\n/** Clears out Glide's disk cache and the Glide singleton after every test method. */\npublic final class TearDownGlide implements TestRule {\n\n  @Override\n  public Statement apply(final Statement base, Description description) {\n    return new Statement() {\n      @Override\n      public void evaluate() throws Throwable {\n        try {\n          base.evaluate();\n        } finally {\n          tearDownGlide();\n        }\n      }\n    };\n  }\n\n  public void tearDownGlide() {\n    ConcurrencyHelper concurrencyHelper = new ConcurrencyHelper();\n    concurrencyHelper.runOnMainThread(\n        new Runnable() {\n          @Override\n          public void run() {\n            // Casting to Context explicitly is required on Java8, or the context will\n            // be interpreted as a FragmentActivity.\n            RequestManager requestManager =\n                Glide.with(ApplicationProvider.<Context>getApplicationContext());\n            requestManager.onStop();\n            requestManager.onDestroy();\n          }\n        });\n    Glide.tearDown();\n  }\n}\n"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/testutil/TestResourceUtil.java",
    "content": "package com.bumptech.glide.testutil;\n\nimport java.io.InputStream;\n\n/** Test only utility for opening resources in androidTest/resources. */\npublic final class TestResourceUtil {\n  private TestResourceUtil() {\n    // Utility class\n  }\n\n  /**\n   * Returns an InputStream for the given test class and sub-path.\n   *\n   * @param testClass A Junit test class.\n   * @param subPath The sub-path under androidTest/resources where the desired resource is located.\n   *     Should not be prefixed with a '/'\n   */\n  public static InputStream openResource(Class<?> testClass, String subPath) {\n    return testClass.getResourceAsStream(\"/\" + subPath);\n  }\n}\n"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/testutil/TestUtil.java",
    "content": "package com.bumptech.glide.testutil;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/** Shared utility classes for tests. */\n// Public API.\n@SuppressWarnings(\"WeakerAccess\")\npublic final class TestUtil {\n  private TestUtil() {\n    // Utility class.\n  }\n\n  public static byte[] resourceToBytes(Class<?> testClass, String resourceName) throws IOException {\n    return isToBytes(TestResourceUtil.openResource(testClass, resourceName));\n  }\n\n  public static byte[] isToBytes(InputStream is) throws IOException {\n    ByteArrayOutputStream os = new ByteArrayOutputStream();\n    byte[] buffer = new byte[1024];\n    int read;\n    try {\n      while ((read = is.read(buffer)) != -1) {\n        os.write(buffer, 0, read);\n      }\n    } finally {\n      is.close();\n    }\n    return os.toByteArray();\n  }\n\n  public static String isToString(InputStream is) throws IOException {\n    return new String(isToBytes(is), \"utf-8\");\n  }\n\n  public static void assertStreamOf(String expected, InputStream result) throws IOException {\n    assertThat(isToString(result)).isEqualTo(expected);\n  }\n}\n"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/testutil/WaitModelLoader.java",
    "content": "package com.bumptech.glide.testutil;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.test.core.app.ApplicationProvider;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.Priority;\nimport com.bumptech.glide.load.DataSource;\nimport com.bumptech.glide.load.Options;\nimport com.bumptech.glide.load.data.DataFetcher;\nimport com.bumptech.glide.load.model.ModelLoader;\nimport com.bumptech.glide.load.model.ModelLoaderFactory;\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory;\nimport com.bumptech.glide.testutil.WaitModelLoader.WaitModel;\nimport java.io.InputStream;\nimport java.util.concurrent.CountDownLatch;\n\n/**\n * Allows callers to load an object but force the load to pause until {@link WaitModel#countDown()}\n * is called.\n */\npublic final class WaitModelLoader<ModelT, DataT> implements ModelLoader<WaitModel<ModelT>, DataT> {\n\n  /**\n   * A Model that can be loaded with Glide where the load will be blocked from completing until\n   * {@link #countDown()} is called.\n   *\n   * <p>This class allows us to test what Glide does while a load is in progress.\n   */\n  public static final class WaitModel<ModelT> {\n    private final CountDownLatch latch = new CountDownLatch(1);\n    private final ModelT wrapped;\n\n    WaitModel(ModelT wrapped) {\n      this.wrapped = wrapped;\n    }\n\n    public void countDown() {\n      if (latch.getCount() != 1) {\n        throw new IllegalStateException();\n      }\n      latch.countDown();\n    }\n  }\n\n  /**\n   * @deprecated Use {@link WaitModelLoaderRule#waitOn(Object)} instead\n   */\n  @Deprecated\n  public static synchronized <T> WaitModel<T> waitOn(T model) {\n    @SuppressWarnings(\"unchecked\")\n    ModelLoaderFactory<WaitModel<T>, InputStream> streamFactory =\n        new Factory<>((Class<T>) model.getClass(), InputStream.class);\n    Glide.get(ApplicationProvider.getApplicationContext())\n        .getRegistry()\n        .replace(WaitModel.class, InputStream.class, streamFactory);\n\n    return new WaitModel<>(model);\n  }\n\n  private final ModelLoader<ModelT, DataT> wrapped;\n\n  private WaitModelLoader(ModelLoader<ModelT, DataT> wrapped) {\n    this.wrapped = wrapped;\n  }\n\n  @Nullable\n  @Override\n  public LoadData<DataT> buildLoadData(\n      @NonNull WaitModel<ModelT> waitModel, int width, int height, @NonNull Options options) {\n    LoadData<DataT> wrappedLoadData =\n        wrapped.buildLoadData(waitModel.wrapped, width, height, options);\n    if (wrappedLoadData == null) {\n      return null;\n    }\n    return new LoadData<>(\n        wrappedLoadData.sourceKey, new WaitFetcher<>(wrappedLoadData.fetcher, waitModel.latch));\n  }\n\n  @Override\n  public boolean handles(@NonNull WaitModel<ModelT> waitModel) {\n    return wrapped.handles(waitModel.wrapped);\n  }\n\n  private static final class Factory<ModelT, DataT>\n      implements ModelLoaderFactory<WaitModel<ModelT>, DataT> {\n\n    private final Class<ModelT> modelClass;\n    private final Class<DataT> dataClass;\n\n    Factory(Class<ModelT> modelClass, Class<DataT> dataClass) {\n      this.modelClass = modelClass;\n      this.dataClass = dataClass;\n    }\n\n    @NonNull\n    @Override\n    public ModelLoader<WaitModel<ModelT>, DataT> build(MultiModelLoaderFactory multiFactory) {\n      return new WaitModelLoader<>(multiFactory.build(modelClass, dataClass));\n    }\n\n    @Override\n    public void teardown() {\n      // Do nothing.\n    }\n  }\n\n  private static final class WaitFetcher<DataT> implements DataFetcher<DataT> {\n\n    private final DataFetcher<DataT> wrapped;\n    private final CountDownLatch toWaitOn;\n\n    WaitFetcher(DataFetcher<DataT> wrapped, CountDownLatch toWaitOn) {\n      this.wrapped = wrapped;\n      this.toWaitOn = toWaitOn;\n    }\n\n    @Override\n    public void loadData(\n        @NonNull Priority priority, @NonNull DataCallback<? super DataT> callback) {\n      ConcurrencyHelper.waitOnLatch(toWaitOn);\n      wrapped.loadData(priority, callback);\n    }\n\n    @Override\n    public void cleanup() {\n      wrapped.cleanup();\n    }\n\n    @Override\n    public void cancel() {\n      wrapped.cancel();\n    }\n\n    @NonNull\n    @Override\n    public Class<DataT> getDataClass() {\n      return wrapped.getDataClass();\n    }\n\n    @NonNull\n    @Override\n    public DataSource getDataSource() {\n      return wrapped.getDataSource();\n    }\n  }\n}\n"
  },
  {
    "path": "testutil/src/main/java/com/bumptech/glide/testutil/WaitModelLoaderRule.java",
    "content": "package com.bumptech.glide.testutil;\n\nimport com.bumptech.glide.testutil.WaitModelLoader.WaitModel;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.rules.ExternalResource;\n\n/** Makes sure that all {@link WaitModel}s created by it are unblocked before the test ends. */\npublic final class WaitModelLoaderRule extends ExternalResource {\n  private final List<WaitModel<?>> waitModels = new ArrayList<>();\n\n  public <T> WaitModel<T> waitOn(T model) {\n    WaitModel<T> waitModel = WaitModelLoader.waitOn(model);\n    waitModels.add(waitModel);\n    return waitModel;\n  }\n\n  @Override\n  protected void after() {\n    super.after();\n    for (WaitModel<?> waitModel : waitModels) {\n      waitModel.countDown();\n    }\n  }\n}\n"
  },
  {
    "path": "third_party/disklrucache/.gitignore",
    "content": "#Eclipse\n.project\n.classpath\n.settings\n.checkstyle\n\n#IntelliJ IDEA\n.idea\n*.iml\n*.ipr\n*.iws\n\n#Maven\ntarget\nrelease.properties\npom.xml.*\n\n#OSX\n.DS_Store\n\n#gradle\nbuild/**\n.gradle/**\n"
  },
  {
    "path": "third_party/disklrucache/CHANGELOG.md",
    "content": "Change Log\n==========\n\nVersion 2.0.2 *(2013-06-18)*\n----------------------------\n\n * Fix: Prevent exception trying to delete a non-existent file.\n\n\nVersion 2.0.1 *(2013-04-27)*\n----------------------------\n\n * Fix: Do not throw runtime exceptions for racy file I/O.\n * Fix: Synchronize calls to `isClosed`.\n\n\nVersion 2.0.0 *(2013-04-13)*\n----------------------------\n\nThe package name is now `com.jakewharton.disklrucache`.\n\n * New: Automatically flush the cache when an edit is completed.\n * Fix: Ensure file handles are not held when a file is not found.\n * Fix: Correct journal rebuilds on Windows.\n * Fix: Ensure file writer uses the appropriate encoding.\n\n\nVersion 1.3.1 *(2013-01-02)*\n----------------------------\n\n * Fix: Correct logic around detecting whether a journal rebuild is required.\n   *(Thanks Jonathan Gerbaud)*\n\n\nVersion 1.3.0 *(2012-12-24)*\n----------------------------\n\n * Re-allow dash in cache key (now `[a-z0-9_-]{1,64}`).\n * New: `getLength` method on `Snapshot`. *(Thanks Edward Dale)*\n * Performance improvements reading journal lines.\n\n\nVersion 1.2.1 *(2012-10-08)*\n----------------------------\n\n * Fix: Ensure library references Java 5-compatible version of\n   `Arrays.copyOfRange`. *(Thanks Edward Dale)*\n\n\nVersion 1.2.0 *(2012-09-30)*\n----------------------------\n\n * New API for cache size adjustment.\n * Keys are now enforced to match `[a-z0-9_]{1,64}` *(Thanks Brian Langel)*\n * Fix: Cache will gracefully recover if directory is deleted at runtime.\n\n\nVersion 1.1.0 *(2012-01-07)*\n----------------------------\n\n * New API for editing an existing snapshot. *(Thanks Jesse Wilson)*\n\n\nVersion 1.0.0 *(2012-01-04)*\n----------------------------\n\nInitial version.\n"
  },
  {
    "path": "third_party/disklrucache/LICENSE",
    "content": "Copyright 2012 Jake Wharton\nCopyright 2011 The Android Open Source Project\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": "third_party/disklrucache/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 2012 Jake Wharton\n   Copyright 2011 The Android Open Source Project\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": "third_party/disklrucache/README.md",
    "content": "Disk LRU Cache\n==============\n\nA cache that uses a bounded amount of space on a filesystem. Each cache entry\nhas a string key and a fixed number of values. Each key must match the regex\n`[a-z0-9_-]{1,64}`.  Values are byte sequences, accessible as streams or files.\nEach value must be between `0` and `Integer.MAX_VALUE` bytes in length.\n\nThe cache stores its data in a directory on the filesystem. This directory must\nbe exclusive to the cache; the cache may delete or overwrite files from its\ndirectory. It is an error for multiple processes to use the same cache\ndirectory at the same time.\n\nThis cache limits the number of bytes that it will store on the filesystem.\nWhen the number of stored bytes exceeds the limit, the cache will remove\nentries in the background until the limit is satisfied. The limit is not\nstrict: the cache may temporarily exceed it while waiting for files to be\ndeleted. The limit does not include filesystem overhead or the cache journal so\nspace-sensitive applications should set a conservative limit.\n\nClients call `edit` to create or update the values of an entry. An entry may\nhave only one editor at one time; if a value is not available to be edited then\n`edit` will return null.\n\n *  When an entry is being **created** it is necessary to supply a full set of\n    values; the empty value should be used as a placeholder if necessary.\n *  When an entry is being **edited**, it is not necessary to supply data for\n    every value; values default to their previous value.\n\nEvery `edit` call must be matched by a call to `Editor.commit` or\n`Editor.abort`. Committing is atomic: a read observes the full set of values as\nthey were before or after the commit, but never a mix of values.\n\nClients call `get` to read a snapshot of an entry. The read will observe the\nvalue at the time that `get` was called. Updates and removals after the call do\nnot impact ongoing reads.\n\nThis class is tolerant of some I/O errors. If files are missing from the\nfilesystem, the corresponding entries will be dropped from the cache. If an\nerror occurs while writing a cache value, the edit will fail silently. Callers\nshould handle other problems by catching `IOException` and responding\nappropriately.\n\n*Note: This implementation specifically targets Android compatibility.*\n\nLicense\n=======\n\n    Copyright 2012 Jake Wharton\n    Copyright 2011 The Android Open Source Project\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"
  },
  {
    "path": "third_party/disklrucache/THIRD_PARTY.md",
    "content": "URL: https://github.com/JakeWharton/DiskLruCache/tarball/7a1ecbd38d2ad0873fb843e911d60235b7434acb\nVersion: 7a1ecbd38d2ad0873fb843e911d60235b7434acb\nLicense: Apache 2.0\nLicense File: LICENSE\n\nDescription:\nJava implementation of a Disk-based LRU cache which specifically targets Android compatibility.\n\nLocal Modifications:\nExposed File objects directly to gets, removed key validation, removed test sources.\n"
  },
  {
    "path": "third_party/disklrucache/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n    checkstyle\n}\n\ncheckstyle {\n    toolVersion = \"6.19\"\n    configFile = file(\"checkstyle.xml\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.disklrucache\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n\n        consumerProguardFiles(\"proguard-rules.txt\")\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    testImplementation(libs.junit)\n    testImplementation(libs.truth)\n}\n\nval uploaderScript = \"${rootProject.projectDir}/scripts/upload.gradle.kts\"\n\nif (file(uploaderScript).exists()) {\n    apply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")\n}"
  },
  {
    "path": "third_party/disklrucache/checkstyle.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC\n    \"-//Puppy Crawl//DTD Check Configuration 1.2//EN\"\n    \"http://www.puppycrawl.com/dtds/configuration_1_2.dtd\">\n\n<module name=\"Checker\">\n    <module name=\"FileLength\"/>\n    <module name=\"FileTabCharacter\"/>\n    <module name=\"NewlineAtEndOfFile\">\n        <property name=\"lineSeparator\" value=\"lf\" />\n    </module>\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)\\b[^ ]\"/>\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        <!-- 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\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        <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\"/> <!-- defaults to sun.* packages -->\n        <module name=\"RedundantImport\"/>\n        <module name=\"UnusedImports\"/>\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        <module name=\"ParameterNumber\"/>\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=\"LeftCurly\"/>\n        <!--<module name=\"NeedBraces\"/>-->\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=\"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>\n"
  },
  {
    "path": "third_party/disklrucache/gradle.properties",
    "content": "POM_NAME=Glide Disk LRU Cache Library\nPOM_ARTIFACT_ID=disklrucache\nPOM_PACKAGING=jar\nPOM_DESCRIPTION=A cache that uses a bounded amount of space on a filesystem. Based on Jake Wharton's tailored for Glide.\n"
  },
  {
    "path": "third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/DiskLruCache.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\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.bumptech.glide.disklrucache;\n\nimport android.annotation.TargetApi;\nimport android.os.Build.VERSION;\nimport android.os.Build.VERSION_CODES;\nimport android.os.StrictMode;\nimport java.io.BufferedWriter;\nimport java.io.Closeable;\nimport java.io.EOFException;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A cache that uses a bounded amount of space on a filesystem. Each cache\n * entry has a string key and a fixed number of values. Each key must match\n * the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,\n * accessible as streams or files. Each value must be between {@code 0} and\n * {@code Integer.MAX_VALUE} bytes in length.\n *\n * <p>The cache stores its data in a directory on the filesystem. This\n * directory must be exclusive to the cache; the cache may delete or overwrite\n * files from its directory. It is an error for multiple processes to use the\n * same cache directory at the same time.\n *\n * <p>This cache limits the number of bytes that it will store on the\n * filesystem. When the number of stored bytes exceeds the limit, the cache will\n * remove entries in the background until the limit is satisfied. The limit is\n * not strict: the cache may temporarily exceed it while waiting for files to be\n * deleted. The limit does not include filesystem overhead or the cache\n * journal so space-sensitive applications should set a conservative limit.\n *\n * <p>Clients call {@link #edit} to create or update the values of an entry. An\n * entry may have only one editor at one time; if a value is not available to be\n * edited then {@link #edit} will return null.\n * <ul>\n * <li>When an entry is being <strong>created</strong> it is necessary to\n * supply a full set of values; the empty value should be used as a\n * placeholder if necessary.\n * <li>When an entry is being <strong>edited</strong>, it is not necessary\n * to supply data for every value; values default to their previous\n * value.\n * </ul>\n * Every {@link #edit} call must be matched by a call to {@link Editor#commit}\n * or {@link Editor#abort}. Committing is atomic: a read observes the full set\n * of values as they were before or after the commit, but never a mix of values.\n *\n * <p>Clients call {@link #get} to read a snapshot of an entry. The read will\n * observe the value at the time that {@link #get} was called. Updates and\n * removals after the call do not impact ongoing reads.\n *\n * <p>This class is tolerant of some I/O errors. If files are missing from the\n * filesystem, the corresponding entries will be dropped from the cache. If\n * an error occurs while writing a cache value, the edit will fail silently.\n * Callers should handle other problems by catching {@code IOException} and\n * responding appropriately.\n */\npublic final class DiskLruCache implements Closeable {\n  static final String JOURNAL_FILE = \"journal\";\n  static final String JOURNAL_FILE_TEMP = \"journal.tmp\";\n  static final String JOURNAL_FILE_BACKUP = \"journal.bkp\";\n  static final String MAGIC = \"libcore.io.DiskLruCache\";\n  static final String VERSION_1 = \"1\";\n  static final long ANY_SEQUENCE_NUMBER = -1;\n  private static final String CLEAN = \"CLEAN\";\n  private static final String DIRTY = \"DIRTY\";\n  private static final String REMOVE = \"REMOVE\";\n  private static final String READ = \"READ\";\n\n    /*\n     * This cache uses a journal file named \"journal\". A typical journal file\n     * looks like this:\n     *     libcore.io.DiskLruCache\n     *     1\n     *     100\n     *     2\n     *\n     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054\n     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52\n     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342\n     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52\n     *     DIRTY 1ab96a171faeeee38496d8b330771a7a\n     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234\n     *     READ 335c4c6028171cfddfbaae1a9c313c52\n     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6\n     *\n     * The first five lines of the journal form its header. They are the\n     * constant string \"libcore.io.DiskLruCache\", the disk cache's version,\n     * the application's version, the value count, and a blank line.\n     *\n     * Each of the subsequent lines in the file is a record of the state of a\n     * cache entry. Each line contains space-separated values: a state, a key,\n     * and optional state-specific values.\n     *   o DIRTY lines track that an entry is actively being created or updated.\n     *     Every successful DIRTY action should be followed by a CLEAN or REMOVE\n     *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that\n     *     temporary files may need to be deleted.\n     *   o CLEAN lines track a cache entry that has been successfully published\n     *     and may be read. A publish line is followed by the lengths of each of\n     *     its values.\n     *   o READ lines track accesses for LRU.\n     *   o REMOVE lines track entries that have been deleted.\n     *\n     * The journal file is appended to as cache operations occur. The journal may\n     * occasionally be compacted by dropping redundant lines. A temporary file named\n     * \"journal.tmp\" will be used during compaction; that file should be deleted if\n     * it exists when the cache is opened.\n     */\n\n  private final File directory;\n  private final File journalFile;\n  private final File journalFileTmp;\n  private final File journalFileBackup;\n  private final int appVersion;\n  private long maxSize;\n  private final int valueCount;\n  private long size = 0;\n  private Writer journalWriter;\n  private final LinkedHashMap<String, Entry> lruEntries =\n      new LinkedHashMap<String, Entry>(0, 0.75f, true);\n  private int redundantOpCount;\n\n  /**\n   * To differentiate between old and current snapshots, each entry is given\n   * a sequence number each time an edit is committed. A snapshot is stale if\n   * its sequence number is not equal to its entry's sequence number.\n   */\n  private long nextSequenceNumber = 0;\n\n  /** This cache uses a single background thread to evict entries. */\n  final ThreadPoolExecutor executorService =\n      new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),\n          new DiskLruCacheThreadFactory());\n  private final Callable<Void> cleanupCallable = new Callable<Void>() {\n    public Void call() throws Exception {\n      synchronized (DiskLruCache.this) {\n        if (journalWriter == null) {\n          return null; // Closed.\n        }\n        trimToSize();\n        if (journalRebuildRequired()) {\n          rebuildJournal();\n          redundantOpCount = 0;\n        }\n      }\n      return null;\n    }\n  };\n\n  private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {\n    this.directory = directory;\n    this.appVersion = appVersion;\n    this.journalFile = new File(directory, JOURNAL_FILE);\n    this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);\n    this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);\n    this.valueCount = valueCount;\n    this.maxSize = maxSize;\n  }\n\n  /**\n   * Opens the cache in {@code directory}, creating a cache if none exists\n   * there.\n   *\n   * @param directory a writable directory\n   * @param valueCount the number of values per cache entry. Must be positive.\n   * @param maxSize the maximum number of bytes this cache should use to store\n   * @throws IOException if reading or writing the cache directory fails\n   */\n  public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)\n      throws IOException {\n    if (maxSize <= 0) {\n      throw new IllegalArgumentException(\"maxSize <= 0\");\n    }\n    if (valueCount <= 0) {\n      throw new IllegalArgumentException(\"valueCount <= 0\");\n    }\n\n    // If a bkp file exists, use it instead.\n    File backupFile = new File(directory, JOURNAL_FILE_BACKUP);\n    if (backupFile.exists()) {\n      File journalFile = new File(directory, JOURNAL_FILE);\n      // If journal file also exists just delete backup file.\n      if (journalFile.exists()) {\n        backupFile.delete();\n      } else {\n        renameTo(backupFile, journalFile, false);\n      }\n    }\n\n    // Prefer to pick up where we left off.\n    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);\n    if (cache.journalFile.exists()) {\n      try {\n        cache.readJournal();\n        cache.processJournal();\n        return cache;\n      } catch (IOException journalIsCorrupt) {\n        System.out\n            .println(\"DiskLruCache \"\n                + directory\n                + \" is corrupt: \"\n                + journalIsCorrupt.getMessage()\n                + \", removing\");\n        cache.delete();\n      }\n    }\n\n    // Create a new empty cache.\n    directory.mkdirs();\n    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);\n    cache.rebuildJournal();\n    return cache;\n  }\n\n  private void readJournal() throws IOException {\n    StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);\n    try {\n      String magic = reader.readLine();\n      String version = reader.readLine();\n      String appVersionString = reader.readLine();\n      String valueCountString = reader.readLine();\n      String blank = reader.readLine();\n      if (!MAGIC.equals(magic)\n          || !VERSION_1.equals(version)\n          || !Integer.toString(appVersion).equals(appVersionString)\n          || !Integer.toString(valueCount).equals(valueCountString)\n          || !\"\".equals(blank)) {\n        throw new IOException(\"unexpected journal header: [\" + magic + \", \" + version + \", \"\n            + valueCountString + \", \" + blank + \"]\");\n      }\n\n      int lineCount = 0;\n      while (true) {\n        try {\n          readJournalLine(reader.readLine());\n          lineCount++;\n        } catch (EOFException endOfJournal) {\n          break;\n        }\n      }\n      redundantOpCount = lineCount - lruEntries.size();\n\n      // If we ended on a truncated line, rebuild the journal before appending to it.\n      if (reader.hasUnterminatedLine()) {\n        rebuildJournal();\n      } else {\n        journalWriter = new BufferedWriter(new OutputStreamWriter(\n            new FileOutputStream(journalFile, true), Util.US_ASCII));\n      }\n    } finally {\n      Util.closeQuietly(reader);\n    }\n  }\n\n  private void readJournalLine(String line) throws IOException {\n    int firstSpace = line.indexOf(' ');\n    if (firstSpace == -1) {\n      throw new IOException(\"unexpected journal line: \" + line);\n    }\n\n    int keyBegin = firstSpace + 1;\n    int secondSpace = line.indexOf(' ', keyBegin);\n    final String key;\n    if (secondSpace == -1) {\n      key = line.substring(keyBegin);\n      if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {\n        lruEntries.remove(key);\n        return;\n      }\n    } else {\n      key = line.substring(keyBegin, secondSpace);\n    }\n\n    Entry entry = lruEntries.get(key);\n    if (entry == null) {\n      entry = new Entry(key);\n      lruEntries.put(key, entry);\n    }\n\n    if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {\n      String[] parts = line.substring(secondSpace + 1).split(\" \");\n      entry.readable = true;\n      entry.currentEditor = null;\n      entry.setLengths(parts);\n    } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {\n      entry.currentEditor = new Editor(entry);\n    } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {\n      // This work was already done by calling lruEntries.get().\n    } else {\n      throw new IOException(\"unexpected journal line: \" + line);\n    }\n  }\n\n  /**\n   * Computes the initial size and collects garbage as a part of opening the\n   * cache. Dirty entries are assumed to be inconsistent and will be deleted.\n   */\n  private void processJournal() throws IOException {\n    deleteIfExists(journalFileTmp);\n    for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {\n      Entry entry = i.next();\n      if (entry.currentEditor == null) {\n        for (int t = 0; t < valueCount; t++) {\n          size += entry.lengths[t];\n        }\n      } else {\n        entry.currentEditor = null;\n        for (int t = 0; t < valueCount; t++) {\n          deleteIfExists(entry.getCleanFile(t));\n          deleteIfExists(entry.getDirtyFile(t));\n        }\n        i.remove();\n      }\n    }\n  }\n\n  /**\n   * Creates a new journal that omits redundant information. This replaces the\n   * current journal if it exists.\n   */\n  private synchronized void rebuildJournal() throws IOException {\n    if (journalWriter != null) {\n      closeWriter(journalWriter);\n    }\n\n    Writer writer = new BufferedWriter(\n        new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));\n    try {\n      writer.write(MAGIC);\n      writer.write(\"\\n\");\n      writer.write(VERSION_1);\n      writer.write(\"\\n\");\n      writer.write(Integer.toString(appVersion));\n      writer.write(\"\\n\");\n      writer.write(Integer.toString(valueCount));\n      writer.write(\"\\n\");\n      writer.write(\"\\n\");\n\n      for (Entry entry : lruEntries.values()) {\n        if (entry.currentEditor != null) {\n          writer.write(DIRTY + ' ' + entry.key + '\\n');\n        } else {\n          writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\\n');\n        }\n      }\n    } finally {\n      closeWriter(writer);\n    }\n\n    if (journalFile.exists()) {\n      renameTo(journalFile, journalFileBackup, true);\n    }\n    renameTo(journalFileTmp, journalFile, false);\n    journalFileBackup.delete();\n\n    journalWriter = new BufferedWriter(\n        new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));\n  }\n\n  private static void deleteIfExists(File file) throws IOException {\n    if (file.exists() && !file.delete()) {\n      throw new IOException();\n    }\n  }\n\n  private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {\n    if (deleteDestination) {\n      deleteIfExists(to);\n    }\n    if (!from.renameTo(to)) {\n      throw new IOException();\n    }\n  }\n\n  /**\n   * Returns a snapshot of the entry named {@code key}, or null if it doesn't\n   * exist is not currently readable. If a value is returned, it is moved to\n   * the head of the LRU queue.\n   */\n  public synchronized Value get(String key) throws IOException {\n    checkNotClosed();\n    Entry entry = lruEntries.get(key);\n    if (entry == null) {\n      return null;\n    }\n\n    if (!entry.readable) {\n      return null;\n    }\n\n    for (File file : entry.cleanFiles) {\n        // A file must have been deleted manually!\n        if (!file.exists()) {\n            return null;\n        }\n    }\n\n    redundantOpCount++;\n    journalWriter.append(READ);\n    journalWriter.append(' ');\n    journalWriter.append(key);\n    journalWriter.append('\\n');\n    if (journalRebuildRequired()) {\n      executorService.submit(cleanupCallable);\n    }\n\n    return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);\n  }\n\n  /**\n   * Returns an editor for the entry named {@code key}, or null if another\n   * edit is in progress.\n   */\n  public Editor edit(String key) throws IOException {\n    return edit(key, ANY_SEQUENCE_NUMBER);\n  }\n\n  private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {\n    checkNotClosed();\n    Entry entry = lruEntries.get(key);\n    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null\n        || entry.sequenceNumber != expectedSequenceNumber)) {\n      return null; // Value is stale.\n    }\n    if (entry == null) {\n      entry = new Entry(key);\n      lruEntries.put(key, entry);\n    } else if (entry.currentEditor != null) {\n      return null; // Another edit is in progress.\n    }\n\n    Editor editor = new Editor(entry);\n    entry.currentEditor = editor;\n\n    // Flush the journal before creating files to prevent file leaks.\n    journalWriter.append(DIRTY);\n    journalWriter.append(' ');\n    journalWriter.append(key);\n    journalWriter.append('\\n');\n    flushWriter(journalWriter);\n    return editor;\n  }\n\n  /** Returns the directory where this cache stores its data. */\n  public File getDirectory() {\n    return directory;\n  }\n\n  /**\n   * Returns the maximum number of bytes that this cache should use to store\n   * its data.\n   */\n  public synchronized long getMaxSize() {\n    return maxSize;\n  }\n\n  /**\n   * Changes the maximum number of bytes the cache can store and queues a job\n   * to trim the existing store, if necessary.\n   */\n  public synchronized void setMaxSize(long maxSize) {\n    this.maxSize = maxSize;\n    executorService.submit(cleanupCallable);\n  }\n\n  /**\n   * Returns the number of bytes currently being used to store the values in\n   * this cache. This may be greater than the max size if a background\n   * deletion is pending.\n   */\n  public synchronized long size() {\n    return size;\n  }\n\n  private synchronized void completeEdit(Editor editor, boolean success) throws IOException {\n    Entry entry = editor.entry;\n    if (entry.currentEditor != editor) {\n      throw new IllegalStateException();\n    }\n\n    // If this edit is creating the entry for the first time, every index must have a value.\n    if (success && !entry.readable) {\n      for (int i = 0; i < valueCount; i++) {\n        if (!editor.written[i]) {\n          editor.abort();\n          throw new IllegalStateException(\"Newly created entry didn't create value for index \" + i);\n        }\n        if (!entry.getDirtyFile(i).exists()) {\n          editor.abort();\n          return;\n        }\n      }\n    }\n\n    for (int i = 0; i < valueCount; i++) {\n      File dirty = entry.getDirtyFile(i);\n      if (success) {\n        if (dirty.exists()) {\n          File clean = entry.getCleanFile(i);\n          dirty.renameTo(clean);\n          long oldLength = entry.lengths[i];\n          long newLength = clean.length();\n          entry.lengths[i] = newLength;\n          size = size - oldLength + newLength;\n        }\n      } else {\n        deleteIfExists(dirty);\n      }\n    }\n\n    redundantOpCount++;\n    entry.currentEditor = null;\n    if (entry.readable | success) {\n      entry.readable = true;\n      journalWriter.append(CLEAN);\n      journalWriter.append(' ');\n      journalWriter.append(entry.key);\n      journalWriter.append(entry.getLengths());\n      journalWriter.append('\\n');\n\n      if (success) {\n        entry.sequenceNumber = nextSequenceNumber++;\n      }\n    } else {\n      lruEntries.remove(entry.key);\n      journalWriter.append(REMOVE);\n      journalWriter.append(' ');\n      journalWriter.append(entry.key);\n      journalWriter.append('\\n');\n    }\n    flushWriter(journalWriter);\n\n    if (size > maxSize || journalRebuildRequired()) {\n      executorService.submit(cleanupCallable);\n    }\n  }\n\n  /**\n   * We only rebuild the journal when it will halve the size of the journal\n   * and eliminate at least 2000 ops.\n   */\n  private boolean journalRebuildRequired() {\n    final int redundantOpCompactThreshold = 2000;\n    return redundantOpCount >= redundantOpCompactThreshold //\n        && redundantOpCount >= lruEntries.size();\n  }\n\n  /**\n   * Drops the entry for {@code key} if it exists and can be removed. Entries\n   * actively being edited cannot be removed.\n   *\n   * @return true if an entry was removed.\n   */\n  public synchronized boolean remove(String key) throws IOException {\n    checkNotClosed();\n    Entry entry = lruEntries.get(key);\n    if (entry == null || entry.currentEditor != null) {\n      return false;\n    }\n\n    for (int i = 0; i < valueCount; i++) {\n      File file = entry.getCleanFile(i);\n      if (file.exists() && !file.delete()) {\n        throw new IOException(\"failed to delete \" + file);\n      }\n      size -= entry.lengths[i];\n      entry.lengths[i] = 0;\n    }\n\n    redundantOpCount++;\n    journalWriter.append(REMOVE);\n    journalWriter.append(' ');\n    journalWriter.append(key);\n    journalWriter.append('\\n');\n\n    lruEntries.remove(key);\n\n    if (journalRebuildRequired()) {\n      executorService.submit(cleanupCallable);\n    }\n\n    return true;\n  }\n\n  /** Returns true if this cache has been closed. */\n  public synchronized boolean isClosed() {\n    return journalWriter == null;\n  }\n\n  private void checkNotClosed() {\n    if (journalWriter == null) {\n      throw new IllegalStateException(\"cache is closed\");\n    }\n  }\n\n  /** Force buffered operations to the filesystem. */\n  public synchronized void flush() throws IOException {\n    checkNotClosed();\n    trimToSize();\n    flushWriter(journalWriter);\n  }\n\n  /** Closes this cache. Stored values will remain on the filesystem. */\n  public synchronized void close() throws IOException {\n    if (journalWriter == null) {\n      return; // Already closed.\n    }\n    for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {\n      if (entry.currentEditor != null) {\n        entry.currentEditor.abort();\n      }\n    }\n    trimToSize();\n    closeWriter(journalWriter);\n    journalWriter = null;\n  }\n\n  private void trimToSize() throws IOException {\n    while (size > maxSize) {\n      Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();\n      remove(toEvict.getKey());\n    }\n  }\n\n  /**\n   * Closes the cache and deletes all of its stored values. This will delete\n   * all files in the cache directory including files that weren't created by\n   * the cache.\n   */\n  public void delete() throws IOException {\n    close();\n    Util.deleteContents(directory);\n  }\n\n  private static String inputStreamToString(InputStream in) throws IOException {\n    return Util.readFully(new InputStreamReader(in, Util.UTF_8));\n  }\n\n  /**\n   * Closes the writer while whitelisting with StrictMode if necessary.\n   *\n   * <p>Analogous to b/71520172.\n   */\n  @TargetApi(VERSION_CODES.O)\n  private static void closeWriter(Writer writer) throws IOException {\n    // If API is less than 26, we don't need to whitelist with StrictMode.\n    if (VERSION.SDK_INT < VERSION_CODES.O) {\n      writer.close();\n      return;\n    }\n\n    StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();\n    StrictMode.ThreadPolicy unbufferedIoPolicy =\n        new StrictMode.ThreadPolicy.Builder(oldPolicy).permitUnbufferedIo().build();\n    StrictMode.setThreadPolicy(unbufferedIoPolicy);\n    try {\n      writer.close();\n    } finally {\n      StrictMode.setThreadPolicy(oldPolicy);\n    }\n  }\n\n  /**\n   * Flushes the writer while whitelisting with StrictMode if necessary.\n   *\n   * <p>See b/71520172.\n   */\n  @TargetApi(VERSION_CODES.O)\n  private static void flushWriter(Writer writer) throws IOException {\n    // If API is less than 26, we don't need to whitelist with StrictMode.\n    if (VERSION.SDK_INT < VERSION_CODES.O) {\n      writer.flush();\n      return;\n    }\n\n    StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();\n    StrictMode.ThreadPolicy unbufferedIoPolicy =\n        new StrictMode.ThreadPolicy.Builder(oldPolicy).permitUnbufferedIo().build();\n    StrictMode.setThreadPolicy(unbufferedIoPolicy);\n    try {\n      writer.flush();\n    } finally {\n      StrictMode.setThreadPolicy(oldPolicy);\n    }\n  }\n\n  /** A snapshot of the values for an entry. */\n  public final class Value {\n    private final String key;\n    private final long sequenceNumber;\n    private final long[] lengths;\n    private final File[] files;\n\n      private Value(String key, long sequenceNumber, File[] files, long[] lengths) {\n      this.key = key;\n      this.sequenceNumber = sequenceNumber;\n      this.files = files;\n      this.lengths = lengths;\n    }\n\n    /**\n     * Returns an editor for this snapshot's entry, or null if either the\n     * entry has changed since this snapshot was created or if another edit\n     * is in progress.\n     */\n    public Editor edit() throws IOException {\n      return DiskLruCache.this.edit(key, sequenceNumber);\n    }\n\n    public File getFile(int index) {\n        return files[index];\n    }\n\n    /** Returns the string value for {@code index}. */\n    public String getString(int index) throws IOException {\n      InputStream is = new FileInputStream(files[index]);\n      return inputStreamToString(is);\n    }\n\n    /** Returns the byte length of the value for {@code index}. */\n    public long getLength(int index) {\n      return lengths[index];\n    }\n  }\n\n  /** Edits the values for an entry. */\n  public final class Editor {\n    private final Entry entry;\n    private final boolean[] written;\n    private boolean committed;\n\n    private Editor(Entry entry) {\n      this.entry = entry;\n      this.written = (entry.readable) ? null : new boolean[valueCount];\n    }\n\n    /**\n     * Returns an unbuffered input stream to read the last committed value,\n     * or null if no value has been committed.\n     */\n    private InputStream newInputStream(int index) throws IOException {\n      synchronized (DiskLruCache.this) {\n        if (entry.currentEditor != this) {\n          throw new IllegalStateException();\n        }\n        if (!entry.readable) {\n          return null;\n        }\n        try {\n          return new FileInputStream(entry.getCleanFile(index));\n        } catch (FileNotFoundException e) {\n          return null;\n        }\n      }\n    }\n\n    /**\n     * Returns the last committed value as a string, or null if no value\n     * has been committed.\n     */\n    public String getString(int index) throws IOException {\n      InputStream in = newInputStream(index);\n      return in != null ? inputStreamToString(in) : null;\n    }\n\n    public File getFile(int index) throws IOException {\n      synchronized (DiskLruCache.this) {\n        if (entry.currentEditor != this) {\n            throw new IllegalStateException();\n        }\n        if (!entry.readable) {\n            written[index] = true;\n        }\n        File dirtyFile = entry.getDirtyFile(index);\n        directory.mkdirs();\n        return dirtyFile;\n      }\n    }\n\n    /** Sets the value at {@code index} to {@code value}. */\n    public void set(int index, String value) throws IOException {\n      Writer writer = null;\n      try {\n        OutputStream os = new FileOutputStream(getFile(index));\n        writer = new OutputStreamWriter(os, Util.UTF_8);\n        writer.write(value);\n      } finally {\n        Util.closeQuietly(writer);\n      }\n    }\n\n    /**\n     * Commits this edit so it is visible to readers.  This releases the\n     * edit lock so another edit may be started on the same key.\n     */\n    public void commit() throws IOException {\n      // The object using this Editor must catch and handle any errors\n      // during the write. If there is an error and they call commit\n      // anyway, we will assume whatever they managed to write was valid.\n      // Normally they should call abort.\n      completeEdit(this, true);\n      committed = true;\n    }\n\n    /**\n     * Aborts this edit. This releases the edit lock so another edit may be\n     * started on the same key.\n     */\n    public void abort() throws IOException {\n      completeEdit(this, false);\n    }\n\n    public void abortUnlessCommitted() {\n      if (!committed) {\n        try {\n          abort();\n        } catch (IOException ignored) {\n        }\n      }\n    }\n  }\n\n  private final class Entry {\n    private final String key;\n\n    /** Lengths of this entry's files. */\n    private final long[] lengths;\n\n    /** Memoized File objects for this entry to avoid char[] allocations. */\n    File[] cleanFiles;\n    File[] dirtyFiles;\n\n    /** True if this entry has ever been published. */\n    private boolean readable;\n\n    /** The ongoing edit or null if this entry is not being edited. */\n    private Editor currentEditor;\n\n    /** The sequence number of the most recently committed edit to this entry. */\n    private long sequenceNumber;\n\n    private Entry(String key) {\n      this.key = key;\n      this.lengths = new long[valueCount];\n      cleanFiles = new File[valueCount];\n      dirtyFiles = new File[valueCount];\n\n      // The names are repetitive so re-use the same builder to avoid allocations.\n      StringBuilder fileBuilder = new StringBuilder(key).append('.');\n      int truncateTo = fileBuilder.length();\n      for (int i = 0; i < valueCount; i++) {\n          fileBuilder.append(i);\n          cleanFiles[i] = new File(directory, fileBuilder.toString());\n          fileBuilder.append(\".tmp\");\n          dirtyFiles[i] = new File(directory, fileBuilder.toString());\n          fileBuilder.setLength(truncateTo);\n      }\n    }\n\n    public String getLengths() throws IOException {\n      StringBuilder result = new StringBuilder();\n      for (long size : lengths) {\n        result.append(' ').append(size);\n      }\n      return result.toString();\n    }\n\n    /** Set lengths using decimal numbers like \"10123\". */\n    private void setLengths(String[] strings) throws IOException {\n      if (strings.length != valueCount) {\n        throw invalidLengths(strings);\n      }\n\n      try {\n        for (int i = 0; i < strings.length; i++) {\n          lengths[i] = Long.parseLong(strings[i]);\n        }\n      } catch (NumberFormatException e) {\n        throw invalidLengths(strings);\n      }\n    }\n\n    private IOException invalidLengths(String[] strings) throws IOException {\n      throw new IOException(\"unexpected journal line: \" + java.util.Arrays.toString(strings));\n    }\n\n    public File getCleanFile(int i) {\n      return cleanFiles[i];\n    }\n\n    public File getDirtyFile(int i) {\n      return dirtyFiles[i];\n    }\n  }\n\n  /**\n   * A {@link java.util.concurrent.ThreadFactory} that builds a thread with a specific thread name\n   * and with minimum priority.\n   */\n  private static final class DiskLruCacheThreadFactory implements ThreadFactory {\n    @Override\n    public synchronized Thread newThread(Runnable runnable) {\n      Thread result = new Thread(runnable, \"glide-disk-lru-cache-thread\");\n      result.setPriority(Thread.MIN_PRIORITY);\n      return result;\n    }\n  }\n}\n"
  },
  {
    "path": "third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/StrictLineReader.java",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\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.bumptech.glide.disklrucache;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.Closeable;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.nio.charset.Charset;\n\n/**\n * Buffers input from an {@link InputStream} for reading lines.\n *\n * <p>This class is used for buffered reading of lines. For purposes of this class, a line ends\n * with \"\\n\" or \"\\r\\n\". End of input is reported by throwing {@code EOFException}. Unterminated\n * line at end of input is invalid and will be ignored, the caller may use {@code\n * hasUnterminatedLine()} to detect it after catching the {@code EOFException}.\n *\n * <p>This class is intended for reading input that strictly consists of lines, such as line-based\n * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction\n * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different\n * end-of-input reporting and a more restrictive definition of a line.\n *\n * <p>This class supports only charsets that encode '\\r' and '\\n' as a single byte with value 13\n * and 10, respectively, and the representation of no other character contains these values.\n * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.\n * The default charset is US_ASCII.\n */\nclass StrictLineReader implements Closeable {\n  private static final byte CR = (byte) '\\r';\n  private static final byte LF = (byte) '\\n';\n\n  private final InputStream in;\n  private final Charset charset;\n\n  /*\n   * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end\n   * and the data in the range [pos, end) is buffered for reading. At end of input, if there is\n   * an unterminated line, we set end == -1, otherwise end == pos. If the underlying\n   * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.\n   */\n  private byte[] buf;\n  private int pos;\n  private int end;\n\n  /**\n   * Constructs a new {@code LineReader} with the specified charset and the default capacity.\n   *\n   * @param in the {@code InputStream} to read data from.\n   * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are\n   * supported.\n   * @throws NullPointerException if {@code in} or {@code charset} is null.\n   * @throws IllegalArgumentException if the specified charset is not supported.\n   */\n  StrictLineReader(InputStream in, Charset charset) {\n    this(in, 8192, charset);\n  }\n\n  /**\n   * Constructs a new {@code LineReader} with the specified capacity and charset.\n   *\n   * @param in the {@code InputStream} to read data from.\n   * @param capacity the capacity of the buffer.\n   * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are\n   * supported.\n   * @throws NullPointerException if {@code in} or {@code charset} is null.\n   * @throws IllegalArgumentException if {@code capacity} is negative or zero\n   * or the specified charset is not supported.\n   */\n  StrictLineReader(InputStream in, int capacity, Charset charset) {\n    if (in == null || charset == null) {\n      throw new NullPointerException();\n    }\n    if (capacity < 0) {\n      throw new IllegalArgumentException(\"capacity <= 0\");\n    }\n    if (!(charset.equals(Util.US_ASCII))) {\n      throw new IllegalArgumentException(\"Unsupported encoding\");\n    }\n\n    this.in = in;\n    this.charset = charset;\n    buf = new byte[capacity];\n  }\n\n  /**\n   * Closes the reader by closing the underlying {@code InputStream} and\n   * marking this reader as closed.\n   *\n   * @throws IOException for errors when closing the underlying {@code InputStream}.\n   */\n  @Override\n  public void close() throws IOException {\n    synchronized (in) {\n      if (buf != null) {\n        buf = null;\n        in.close();\n      }\n    }\n  }\n\n  /**\n   * Reads the next line. A line ends with {@code \"\\n\"} or {@code \"\\r\\n\"},\n   * this end of line marker is not included in the result.\n   *\n   * @return the next line from the input.\n   * @throws IOException for underlying {@code InputStream} errors.\n   * @throws EOFException for the end of source stream.\n   */\n  String readLine() throws IOException {\n    synchronized (in) {\n      if (buf == null) {\n        throw new IOException(\"LineReader is closed\");\n      }\n\n      // Read more data if we are at the end of the buffered data.\n      // Though it's an error to read after an exception, we will let {@code fillBuf()}\n      // throw again if that happens; thus we need to handle end == -1 as well as end == pos.\n      if (pos >= end) {\n        fillBuf();\n      }\n      // Try to find LF in the buffered data and return the line if successful.\n      for (int i = pos; i != end; ++i) {\n        if (buf[i] == LF) {\n          int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;\n          String res = new String(buf, pos, lineEnd - pos, charset.name());\n          pos = i + 1;\n          return res;\n        }\n      }\n\n      // Let's anticipate up to 80 characters on top of those already read.\n      ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {\n        @Override\n        public String toString() {\n          int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;\n          try {\n            return new String(buf, 0, length, charset.name());\n          } catch (UnsupportedEncodingException e) {\n            throw new AssertionError(e); // Since we control the charset this will never happen.\n          }\n        }\n      };\n\n      while (true) {\n        out.write(buf, pos, end - pos);\n        // Mark unterminated line in case fillBuf throws EOFException or IOException.\n        end = -1;\n        fillBuf();\n        // Try to find LF in the buffered data and return the line if successful.\n        for (int i = pos; i != end; ++i) {\n          if (buf[i] == LF) {\n            if (i != pos) {\n              out.write(buf, pos, i - pos);\n            }\n            pos = i + 1;\n            return out.toString();\n          }\n        }\n      }\n    }\n  }\n\n  boolean hasUnterminatedLine() {\n    return end == -1;\n  }\n\n  /**\n   * Reads new input data into the buffer. Call only with pos == end or end == -1,\n   * depending on the desired outcome if the function throws.\n   */\n  private void fillBuf() throws IOException {\n    int result = in.read(buf, 0, buf.length);\n    if (result == -1) {\n      throw new EOFException();\n    }\n    pos = 0;\n    end = result;\n  }\n}\n\n"
  },
  {
    "path": "third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/Util.java",
    "content": "/*\n * Copyright (C) 2010 The Android Open Source Project\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.bumptech.glide.disklrucache;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringWriter;\nimport java.nio.charset.Charset;\n\n/** Junk drawer of utility methods. */\nfinal class Util {\n  static final Charset US_ASCII = Charset.forName(\"US-ASCII\");\n  static final Charset UTF_8 = Charset.forName(\"UTF-8\");\n\n  private Util() {\n  }\n\n  static String readFully(Reader reader) throws IOException {\n    try {\n      StringWriter writer = new StringWriter();\n      char[] buffer = new char[1024];\n      int count;\n      while ((count = reader.read(buffer)) != -1) {\n        writer.write(buffer, 0, count);\n      }\n      return writer.toString();\n    } finally {\n      reader.close();\n    }\n  }\n\n  /**\n   * Deletes the contents of {@code dir}. Throws an IOException if any file\n   * could not be deleted, or if {@code dir} is not a readable directory.\n   */\n  static void deleteContents(File dir) throws IOException {\n    File[] files = dir.listFiles();\n    if (files == null) {\n      throw new IOException(\"not a readable directory: \" + dir);\n    }\n    for (File file : files) {\n      if (file.isDirectory()) {\n        deleteContents(file);\n      }\n      if (!file.delete()) {\n        throw new IOException(\"failed to delete file: \" + file);\n      }\n    }\n  }\n\n  static void closeQuietly(/*Auto*/Closeable closeable) {\n    if (closeable != null) {\n      try {\n        closeable.close();\n      } catch (RuntimeException rethrown) {\n        throw rethrown;\n      } catch (Exception ignored) {\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "third_party/disklrucache/src/test/java/com/bumptech/glide/disklrucache/DiskLruCacheTest.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\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.bumptech.glide.disklrucache;\n\nimport static com.bumptech.glide.disklrucache.DiskLruCache.JOURNAL_FILE;\nimport static com.bumptech.glide.disklrucache.DiskLruCache.JOURNAL_FILE_BACKUP;\nimport static com.bumptech.glide.disklrucache.DiskLruCache.MAGIC;\nimport static com.bumptech.glide.disklrucache.DiskLruCache.VERSION_1;\nimport static com.google.common.truth.Fact.simpleFact;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.hamcrest.core.IsNot.not;\nimport static org.junit.Assume.assumeThat;\n\nimport com.google.common.truth.ComparableSubject;\nimport com.google.common.truth.FailureMetadata;\nimport com.google.common.truth.Subject;\nimport com.google.common.truth.Truth;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileReader;\nimport java.io.FileWriter;\nimport java.io.Reader;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.nio.file.Files;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport org.hamcrest.core.StringStartsWith;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic final class DiskLruCacheTest {\n  private final int appVersion = 100;\n  private File cacheDir;\n  private File journalFile;\n  private File journalBkpFile;\n  private DiskLruCache cache;\n\n  @Rule public TemporaryFolder tempDir = new TemporaryFolder();\n\n  @BeforeClass\n  public static void setUpClass() {\n    assumeThat(System.getProperty(\"os.name\"), not(StringStartsWith.startsWith(\"Windows\")));\n  }\n\n  @Before public void setUp() throws Exception {\n    cacheDir = tempDir.newFolder(\"DiskLruCacheTest\");\n    journalFile = new File(cacheDir, JOURNAL_FILE);\n    journalBkpFile = new File(cacheDir, JOURNAL_FILE_BACKUP);\n    for (File file : cacheDir.listFiles()) {\n      file.delete();\n    }\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n  }\n\n  @After public void tearDown() throws Exception {\n    cache.close();\n  }\n\n  @Test public void emptyCache() throws Exception {\n    cache.close();\n    assertJournalEquals();\n  }\n\n  @Test public void writeAndReadEntry() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    creator.set(0, \"ABC\");\n    creator.set(1, \"DE\");\n    assertThat(creator.getString(0)).isNull();\n    assertThat(creator.getString(1)).isNull();\n    creator.commit();\n\n    DiskLruCache.Value value = cache.get(\"k1\");\n    assertThat(value.getString(0)).isEqualTo(\"ABC\");\n    assertThat(value.getLength(0)).isEqualTo(3);\n    assertThat(value.getString(1)).isEqualTo(\"DE\");\n    assertThat(value.getLength(1)).isEqualTo(2);\n  }\n\n  @Test public void readAndWriteEntryAcrossCacheOpenAndClose() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    creator.set(0, \"A\");\n    creator.set(1, \"B\");\n    creator.commit();\n    cache.close();\n\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    DiskLruCache.Value value = cache.get(\"k1\");\n    assertThat(value.getString(0)).isEqualTo(\"A\");\n    assertThat(value.getLength(0)).isEqualTo(1);\n    assertThat(value.getString(1)).isEqualTo(\"B\");\n    assertThat(value.getLength(1)).isEqualTo(1);\n  }\n\n  @Test public void readAndWriteEntryWithoutProperClose() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    creator.set(0, \"A\");\n    creator.set(1, \"B\");\n    creator.commit();\n\n    // Simulate a dirty close of 'cache' by opening the cache directory again.\n    DiskLruCache cache2 = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    DiskLruCache.Value value = cache2.get(\"k1\");\n    assertThat(value.getString(0)).isEqualTo(\"A\");\n    assertThat(value.getLength(0)).isEqualTo(1);\n    assertThat(value.getString(1)).isEqualTo(\"B\");\n    assertThat(value.getLength(1)).isEqualTo(1);\n    cache2.close();\n  }\n\n  @Test public void journalWithEditAndPublish() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    assertJournalEquals(\"DIRTY k1\"); // DIRTY must always be flushed.\n    creator.set(0, \"AB\");\n    creator.set(1, \"C\");\n    creator.commit();\n    cache.close();\n    assertJournalEquals(\"DIRTY k1\", \"CLEAN k1 2 1\");\n  }\n\n  @Test public void revertedNewFileIsRemoveInJournal() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    assertJournalEquals(\"DIRTY k1\"); // DIRTY must always be flushed.\n    creator.set(0, \"AB\");\n    creator.set(1, \"C\");\n    creator.abort();\n    cache.close();\n    assertJournalEquals(\"DIRTY k1\", \"REMOVE k1\");\n  }\n\n  @Test public void unterminatedEditIsRevertedOnClose() throws Exception {\n    cache.edit(\"k1\");\n    cache.close();\n    assertJournalEquals(\"DIRTY k1\", \"REMOVE k1\");\n  }\n\n  @Test public void journalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    assertThat(cache.get(\"k1\")).isNull();\n    creator.set(0, \"A\");\n    creator.set(1, \"BC\");\n    creator.commit();\n    cache.close();\n    assertJournalEquals(\"DIRTY k1\", \"CLEAN k1 1 2\");\n  }\n\n  @Test public void journalWithEditAndPublishAndRead() throws Exception {\n    DiskLruCache.Editor k1Creator = cache.edit(\"k1\");\n    k1Creator.set(0, \"AB\");\n    k1Creator.set(1, \"C\");\n    k1Creator.commit();\n    DiskLruCache.Editor k2Creator = cache.edit(\"k2\");\n    k2Creator.set(0, \"DEF\");\n    k2Creator.set(1, \"G\");\n    k2Creator.commit();\n    DiskLruCache.Value k1Value = cache.get(\"k1\");\n    cache.close();\n    assertJournalEquals(\"DIRTY k1\", \"CLEAN k1 2 1\", \"DIRTY k2\", \"CLEAN k2 3 1\", \"READ k1\");\n  }\n\n  @Test public void cannotOperateOnEditAfterPublish() throws Exception {\n    DiskLruCache.Editor editor = cache.edit(\"k1\");\n    editor.set(0, \"A\");\n    editor.set(1, \"B\");\n    editor.commit();\n    assertInoperable(editor);\n  }\n\n  @Test public void cannotOperateOnEditAfterRevert() throws Exception {\n    DiskLruCache.Editor editor = cache.edit(\"k1\");\n    editor.set(0, \"A\");\n    editor.set(1, \"B\");\n    editor.abort();\n    assertInoperable(editor);\n  }\n\n  @Test public void explicitRemoveAppliedToDiskImmediately() throws Exception {\n    DiskLruCache.Editor editor = cache.edit(\"k1\");\n    editor.set(0, \"ABC\");\n    editor.set(1, \"B\");\n    editor.commit();\n    File k1 = getCleanFile(\"k1\", 0);\n    assertThat(readFile(k1)).isEqualTo(\"ABC\");\n    cache.remove(\"k1\");\n    assertThat(k1.exists()).isFalse();\n  }\n\n  @Test public void openWithDirtyKeyDeletesAllFilesForThatKey() throws Exception {\n    cache.close();\n    File cleanFile0 = getCleanFile(\"k1\", 0);\n    File cleanFile1 = getCleanFile(\"k1\", 1);\n    File dirtyFile0 = getDirtyFile(\"k1\", 0);\n    File dirtyFile1 = getDirtyFile(\"k1\", 1);\n    writeFile(cleanFile0, \"A\");\n    writeFile(cleanFile1, \"B\");\n    writeFile(dirtyFile0, \"C\");\n    writeFile(dirtyFile1, \"D\");\n    createJournal(\"CLEAN k1 1 1\", \"DIRTY   k1\");\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertThat(cleanFile0.exists()).isFalse();\n    assertThat(cleanFile1.exists()).isFalse();\n    assertThat(dirtyFile0.exists()).isFalse();\n    assertThat(dirtyFile1.exists()).isFalse();\n    assertThat(cache.get(\"k1\")).isNull();\n  }\n\n  @Test public void openWithInvalidVersionClearsDirectory() throws Exception {\n    cache.close();\n    generateSomeGarbageFiles();\n    createJournalWithHeader(MAGIC, \"0\", \"100\", \"2\", \"\");\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertGarbageFilesAllDeleted();\n  }\n\n  @Test public void openWithInvalidAppVersionClearsDirectory() throws Exception {\n    cache.close();\n    generateSomeGarbageFiles();\n    createJournalWithHeader(MAGIC, \"1\", \"101\", \"2\", \"\");\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertGarbageFilesAllDeleted();\n  }\n\n  @Test public void openWithInvalidValueCountClearsDirectory() throws Exception {\n    cache.close();\n    generateSomeGarbageFiles();\n    createJournalWithHeader(MAGIC, \"1\", \"100\", \"1\", \"\");\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertGarbageFilesAllDeleted();\n  }\n\n  @Test public void openWithInvalidBlankLineClearsDirectory() throws Exception {\n    cache.close();\n    generateSomeGarbageFiles();\n    createJournalWithHeader(MAGIC, \"1\", \"100\", \"2\", \"x\");\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertGarbageFilesAllDeleted();\n  }\n\n  @Test public void openWithInvalidJournalLineClearsDirectory() throws Exception {\n    cache.close();\n    generateSomeGarbageFiles();\n    createJournal(\"CLEAN k1 1 1\", \"BOGUS\");\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertGarbageFilesAllDeleted();\n    assertThat(cache.get(\"k1\")).isNull();\n  }\n\n  @Test public void openWithInvalidFileSizeClearsDirectory() throws Exception {\n    cache.close();\n    generateSomeGarbageFiles();\n    createJournal(\"CLEAN k1 0000x001 1\");\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertGarbageFilesAllDeleted();\n    assertThat(cache.get(\"k1\")).isNull();\n  }\n\n  @Test public void openWithTruncatedLineDiscardsThatLine() throws Exception {\n    cache.close();\n    writeFile(getCleanFile(\"k1\", 0), \"A\");\n    writeFile(getCleanFile(\"k1\", 1), \"B\");\n    Writer writer = new FileWriter(journalFile);\n    writer.write(MAGIC + \"\\n\" + VERSION_1 + \"\\n100\\n2\\n\\nCLEAN k1 1 1\"); // no trailing newline\n    writer.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertThat(cache.get(\"k1\")).isNull();\n\n    // The journal is not corrupt when editing after a truncated line.\n    set(\"k1\", \"C\", \"D\");\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertValue(\"k1\", \"C\", \"D\");\n  }\n\n  @Test public void openWithTooManyFileSizesClearsDirectory() throws Exception {\n    cache.close();\n    generateSomeGarbageFiles();\n    createJournal(\"CLEAN k1 1 1 1\");\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n    assertGarbageFilesAllDeleted();\n    assertThat(cache.get(\"k1\")).isNull();\n  }\n\n  @Test public void nullKeyThrows() throws Exception {\n    try {\n      cache.edit(null);\n      Assert.fail();\n    } catch (NullPointerException expected) {\n    }\n  }\n\n  @Test public void createNewEntryWithTooFewValuesFails() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    creator.set(1, \"A\");\n    try {\n      creator.commit();\n      Assert.fail();\n    } catch (IllegalStateException expected) {\n    }\n\n    assertThat(getCleanFile(\"k1\", 0).exists()).isFalse();\n    assertThat(getCleanFile(\"k1\", 1).exists()).isFalse();\n    assertThat(getDirtyFile(\"k1\", 0).exists()).isFalse();\n    assertThat(getDirtyFile(\"k1\", 1).exists()).isFalse();\n    assertThat(cache.get(\"k1\")).isNull();\n\n    DiskLruCache.Editor creator2 = cache.edit(\"k1\");\n    creator2.set(0, \"B\");\n    creator2.set(1, \"C\");\n    creator2.commit();\n  }\n\n  @Test public void revertWithTooFewValues() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    creator.set(1, \"A\");\n    creator.abort();\n    assertThat(getCleanFile(\"k1\", 0).exists()).isFalse();\n    assertThat(getCleanFile(\"k1\", 1).exists()).isFalse();\n    assertThat(getDirtyFile(\"k1\", 0).exists()).isFalse();\n    assertThat(getDirtyFile(\"k1\", 1).exists()).isFalse();\n    assertThat(cache.get(\"k1\")).isNull();\n  }\n\n  @Test public void updateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    creator.set(0, \"A\");\n    creator.set(1, \"B\");\n    creator.commit();\n\n    DiskLruCache.Editor updater = cache.edit(\"k1\");\n    updater.set(0, \"C\");\n    updater.commit();\n\n    DiskLruCache.Value value = cache.get(\"k1\");\n    assertThat(value.getString(0)).isEqualTo(\"C\");\n    assertThat(value.getLength(0)).isEqualTo(1);\n    assertThat(value.getString(1)).isEqualTo(\"B\");\n    assertThat(value.getLength(1)).isEqualTo(1);\n  }\n\n  @Test public void growMaxSize() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n    set(\"a\", \"a\", \"aaa\"); // size 4\n    set(\"b\", \"bb\", \"bbbb\"); // size 6\n    cache.setMaxSize(20);\n    set(\"c\", \"c\", \"c\"); // size 12\n    assertThat(cache.size()).isEqualTo(12);\n  }\n\n  @Test public void shrinkMaxSizeEvicts() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 20);\n    set(\"a\", \"a\", \"aaa\"); // size 4\n    set(\"b\", \"bb\", \"bbbb\"); // size 6\n    set(\"c\", \"c\", \"c\"); // size 12\n    cache.setMaxSize(10);\n    cache.executorService.shutdown();\n    cache.executorService.awaitTermination(500, TimeUnit.MILLISECONDS);\n    assertThat(cache.size()).isEqualTo(8 /* 12 - 4 */);\n  }\n\n  @Test public void evictOnInsert() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n\n    set(\"a\", \"a\", \"aaa\"); // size 4\n    set(\"b\", \"bb\", \"bbbb\"); // size 6\n    assertThat(cache.size()).isEqualTo(10);\n\n    // Cause the size to grow to 12 should evict 'A'.\n    set(\"c\", \"c\", \"c\");\n    cache.flush();\n    assertThat(cache.size()).isEqualTo(8);\n    assertAbsent(\"a\");\n    assertValue(\"b\", \"bb\", \"bbbb\");\n    assertValue(\"c\", \"c\", \"c\");\n\n    // Causing the size to grow to 10 should evict nothing.\n    set(\"d\", \"d\", \"d\");\n    cache.flush();\n    assertThat(cache.size()).isEqualTo(10);\n    assertAbsent(\"a\");\n    assertValue(\"b\", \"bb\", \"bbbb\");\n    assertValue(\"c\", \"c\", \"c\");\n    assertValue(\"d\", \"d\", \"d\");\n\n    // Causing the size to grow to 18 should evict 'B' and 'C'.\n    set(\"e\", \"eeee\", \"eeee\");\n    cache.flush();\n    assertThat(cache.size()).isEqualTo(10);\n    assertAbsent(\"a\");\n    assertAbsent(\"b\");\n    assertAbsent(\"c\");\n    assertValue(\"d\", \"d\", \"d\");\n    assertValue(\"e\", \"eeee\", \"eeee\");\n  }\n\n  @Test public void evictOnUpdate() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n\n    set(\"a\", \"a\", \"aa\"); // size 3\n    set(\"b\", \"b\", \"bb\"); // size 3\n    set(\"c\", \"c\", \"cc\"); // size 3\n    assertThat(cache.size()).isEqualTo(9);\n\n    // Causing the size to grow to 11 should evict 'A'.\n    set(\"b\", \"b\", \"bbbb\");\n    cache.flush();\n    assertThat(cache.size()).isEqualTo(8);\n    assertAbsent(\"a\");\n    assertValue(\"b\", \"b\", \"bbbb\");\n    assertValue(\"c\", \"c\", \"cc\");\n  }\n\n  @Test public void evictionHonorsLruFromCurrentSession() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n    set(\"a\", \"a\", \"a\");\n    set(\"b\", \"b\", \"b\");\n    set(\"c\", \"c\", \"c\");\n    set(\"d\", \"d\", \"d\");\n    set(\"e\", \"e\", \"e\");\n    cache.get(\"b\"); // 'B' is now least recently used.\n\n    // Causing the size to grow to 12 should evict 'A'.\n    set(\"f\", \"f\", \"f\");\n    // Causing the size to grow to 12 should evict 'C'.\n    set(\"g\", \"g\", \"g\");\n    cache.flush();\n    assertThat(cache.size()).isEqualTo(10);\n    assertAbsent(\"a\");\n    assertValue(\"b\", \"b\", \"b\");\n    assertAbsent(\"c\");\n    assertValue(\"d\", \"d\", \"d\");\n    assertValue(\"e\", \"e\", \"e\");\n    assertValue(\"f\", \"f\", \"f\");\n  }\n\n  @Test public void evictionHonorsLruFromPreviousSession() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    set(\"b\", \"b\", \"b\");\n    set(\"c\", \"c\", \"c\");\n    set(\"d\", \"d\", \"d\");\n    set(\"e\", \"e\", \"e\");\n    set(\"f\", \"f\", \"f\");\n    cache.get(\"b\"); // 'B' is now least recently used.\n    assertThat(cache.size()).isEqualTo(12);\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n\n    set(\"g\", \"g\", \"g\");\n    cache.flush();\n    assertThat(cache.size()).isEqualTo(10);\n    assertAbsent(\"a\");\n    assertValue(\"b\", \"b\", \"b\");\n    assertAbsent(\"c\");\n    assertValue(\"d\", \"d\", \"d\");\n    assertValue(\"e\", \"e\", \"e\");\n    assertValue(\"f\", \"f\", \"f\");\n    assertValue(\"g\", \"g\", \"g\");\n  }\n\n  @Test public void cacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n    set(\"a\", \"aaaaa\", \"aaaaaa\"); // size=11\n    cache.flush();\n    assertAbsent(\"a\");\n  }\n\n  @Test public void cacheSingleValueOfSizeGreaterThanMaxSize() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n    set(\"a\", \"aaaaaaaaaaa\", \"a\"); // size=12\n    cache.flush();\n    assertAbsent(\"a\");\n  }\n\n  @Test public void constructorDoesNotAllowZeroCacheSize() throws Exception {\n    try {\n      DiskLruCache.open(cacheDir, appVersion, 2, 0);\n      Assert.fail();\n    } catch (IllegalArgumentException expected) {\n    }\n  }\n\n  @Test public void constructorDoesNotAllowZeroValuesPerEntry() throws Exception {\n    try {\n      DiskLruCache.open(cacheDir, appVersion, 0, 10);\n      Assert.fail();\n    } catch (IllegalArgumentException expected) {\n    }\n  }\n\n  @Test public void removeAbsentElement() throws Exception {\n    cache.remove(\"a\");\n  }\n\n  @Test public void readingTheSameFileMultipleTimes() throws Exception {\n    set(\"a\", \"a\", \"b\");\n    DiskLruCache.Value value = cache.get(\"a\");\n    assertThat(value.getFile(0)).isSameInstanceAs(value.getFile(0));\n  }\n\n  @Test public void rebuildJournalOnRepeatedReads() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    set(\"b\", \"b\", \"b\");\n    long lastJournalLength = 0;\n    while (true) {\n      long journalLength = journalFile.length();\n      assertValue(\"a\", \"a\", \"a\");\n      assertValue(\"b\", \"b\", \"b\");\n      if (journalLength < lastJournalLength) {\n        System.out\n            .printf(\"Journal compacted from %s bytes to %s bytes\\n\", lastJournalLength,\n                journalLength);\n        break; // Test passed!\n      }\n      lastJournalLength = journalLength;\n    }\n  }\n\n  @Test public void rebuildJournalOnRepeatedEdits() throws Exception {\n    long lastJournalLength = 0;\n    while (true) {\n      long journalLength = journalFile.length();\n      set(\"a\", \"a\", \"a\");\n      set(\"b\", \"b\", \"b\");\n      if (journalLength < lastJournalLength) {\n        System.out\n            .printf(\"Journal compacted from %s bytes to %s bytes\\n\", lastJournalLength,\n                journalLength);\n        break;\n      }\n      lastJournalLength = journalLength;\n    }\n\n    // Sanity check that a rebuilt journal behaves normally.\n    assertValue(\"a\", \"a\", \"a\");\n    assertValue(\"b\", \"b\", \"b\");\n  }\n\n  /** @see <a href=\"https://github.com/JakeWharton/DiskLruCache/issues/28\">Issue #28</a> */\n  @Test public void rebuildJournalOnRepeatedReadsWithOpenAndClose() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    set(\"b\", \"b\", \"b\");\n    long lastJournalLength = 0;\n    while (true) {\n      long journalLength = journalFile.length();\n      assertValue(\"a\", \"a\", \"a\");\n      assertValue(\"b\", \"b\", \"b\");\n      cache.close();\n      cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n      if (journalLength < lastJournalLength) {\n        System.out\n            .printf(\"Journal compacted from %s bytes to %s bytes\\n\", lastJournalLength,\n                journalLength);\n        break; // Test passed!\n      }\n      lastJournalLength = journalLength;\n    }\n  }\n\n  /** @see <a href=\"https://github.com/JakeWharton/DiskLruCache/issues/28\">Issue #28</a> */\n  @Test public void rebuildJournalOnRepeatedEditsWithOpenAndClose() throws Exception {\n    long lastJournalLength = 0;\n    while (true) {\n      long journalLength = journalFile.length();\n      set(\"a\", \"a\", \"a\");\n      set(\"b\", \"b\", \"b\");\n      cache.close();\n      cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n      if (journalLength < lastJournalLength) {\n        System.out\n            .printf(\"Journal compacted from %s bytes to %s bytes\\n\", lastJournalLength,\n                journalLength);\n        break;\n      }\n      lastJournalLength = journalLength;\n    }\n  }\n\n  @Test public void restoreBackupFile() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    creator.set(0, \"ABC\");\n    creator.set(1, \"DE\");\n    creator.commit();\n    cache.close();\n\n    assertThat(journalFile.renameTo(journalBkpFile)).isTrue();\n    assertThat(journalFile.exists()).isFalse();\n\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n\n    DiskLruCache.Value value = cache.get(\"k1\");\n    assertThat(value.getString(0)).isEqualTo(\"ABC\");\n    assertThat(value.getLength(0)).isEqualTo(3);\n    assertThat(value.getString(1)).isEqualTo(\"DE\");\n    assertThat(value.getLength(1)).isEqualTo(2);\n\n    assertThat(journalBkpFile.exists()).isFalse();\n    assertThat(journalFile.exists()).isTrue();\n  }\n\n  @Test public void journalFileIsPreferredOverBackupFile() throws Exception {\n    DiskLruCache.Editor creator = cache.edit(\"k1\");\n    creator.set(0, \"ABC\");\n    creator.set(1, \"DE\");\n    creator.commit();\n    cache.flush();\n\n    Files.copy(journalFile.toPath(), journalBkpFile.toPath());\n\n    creator = cache.edit(\"k2\");\n    creator.set(0, \"F\");\n    creator.set(1, \"GH\");\n    creator.commit();\n    cache.close();\n\n    assertThat(journalFile.exists()).isTrue();\n    assertThat(journalBkpFile.exists()).isTrue();\n\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);\n\n    DiskLruCache.Value valueA = cache.get(\"k1\");\n    assertThat(valueA.getString(0)).isEqualTo(\"ABC\");\n    assertThat(valueA.getLength(0)).isEqualTo(3);\n    assertThat(valueA.getString(1)).isEqualTo(\"DE\");\n    assertThat(valueA.getLength(1)).isEqualTo(2);\n\n    DiskLruCache.Value valueB = cache.get(\"k2\");\n    assertThat(valueB.getString(0)).isEqualTo(\"F\");\n    assertThat(valueB.getLength(0)).isEqualTo(1);\n    assertThat(valueB.getString(1)).isEqualTo(\"GH\");\n    assertThat(valueB.getLength(1)).isEqualTo(2);\n\n    assertThat(journalBkpFile.exists()).isFalse();\n    assertThat(journalFile.exists()).isTrue();\n  }\n\n  @Test public void openCreatesDirectoryIfNecessary() throws Exception {\n    cache.close();\n    File dir = tempDir.newFolder(\"testOpenCreatesDirectoryIfNecessary\");\n    cache = DiskLruCache.open(dir, appVersion, 2, Integer.MAX_VALUE);\n    set(\"a\", \"a\", \"a\");\n    assertThat(new File(dir, \"a.0\").exists()).isTrue();\n    assertThat(new File(dir, \"a.1\").exists()).isTrue();\n    assertThat(new File(dir, \"journal\").exists()).isTrue();\n  }\n\n  @Test public void fileDeletedExternally() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    getCleanFile(\"a\", 1).delete();\n    assertThat(cache.get(\"a\")).isNull();\n  }\n\n  @Test public void editSameVersion() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    DiskLruCache.Value value = cache.get(\"a\");\n    DiskLruCache.Editor editor = value.edit();\n    editor.set(1, \"a2\");\n    editor.commit();\n    assertValue(\"a\", \"a\", \"a2\");\n  }\n\n  @Test public void editSnapshotAfterChangeAborted() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    DiskLruCache.Value value = cache.get(\"a\");\n    DiskLruCache.Editor toAbort = value.edit();\n    toAbort.set(0, \"b\");\n    toAbort.abort();\n    DiskLruCache.Editor editor = value.edit();\n    editor.set(1, \"a2\");\n    editor.commit();\n    assertValue(\"a\", \"a\", \"a2\");\n  }\n\n  @Test public void editSnapshotAfterChangeCommitted() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    DiskLruCache.Value value = cache.get(\"a\");\n    DiskLruCache.Editor toAbort = value.edit();\n    toAbort.set(0, \"b\");\n    toAbort.commit();\n    assertThat(value.edit()).isNull();\n  }\n\n  @Test public void editSinceEvicted() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n    set(\"a\", \"aa\", \"aaa\"); // size 5\n    DiskLruCache.Value value = cache.get(\"a\");\n    set(\"b\", \"bb\", \"bbb\"); // size 5\n    set(\"c\", \"cc\", \"ccc\"); // size 5; will evict 'A'\n    cache.flush();\n    assertThat(value.edit()).isNull();\n  }\n\n  @Test public void editSinceEvictedAndRecreated() throws Exception {\n    cache.close();\n    cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);\n    set(\"a\", \"aa\", \"aaa\"); // size 5\n    DiskLruCache.Value value = cache.get(\"a\");\n    set(\"b\", \"bb\", \"bbb\"); // size 5\n    set(\"c\", \"cc\", \"ccc\"); // size 5; will evict 'A'\n    set(\"a\", \"a\", \"aaaa\"); // size 5; will evict 'B'\n    cache.flush();\n    assertThat(value.edit()).isNull();\n  }\n\n  /** @see <a href=\"https://github.com/JakeWharton/DiskLruCache/issues/2\">Issue #2</a> */\n  @Test public void aggressiveClearingHandlesWrite() throws Exception {\n    deleteDirectory(cacheDir);\n    set(\"a\", \"a\", \"a\");\n    assertValue(\"a\", \"a\", \"a\");\n  }\n\n  /** @see <a href=\"https://github.com/JakeWharton/DiskLruCache/issues/2\">Issue #2</a> */\n  @Test public void aggressiveClearingHandlesEdit() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    DiskLruCache.Editor a = cache.get(\"a\").edit();\n    deleteDirectory(cacheDir);\n    a.set(1, \"a2\");\n    a.commit();\n  }\n\n  @Test public void removeHandlesMissingFile() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    getCleanFile(\"a\", 0).delete();\n    cache.remove(\"a\");\n  }\n\n  /** @see <a href=\"https://github.com/JakeWharton/DiskLruCache/issues/2\">Issue #2</a> */\n  @Test public void aggressiveClearingHandlesPartialEdit() throws Exception {\n    set(\"a\", \"a\", \"a\");\n    set(\"b\", \"b\", \"b\");\n    DiskLruCache.Editor a = cache.get(\"a\").edit();\n    a.set(0, \"a1\");\n    deleteDirectory(cacheDir);\n    a.set(1, \"a2\");\n    a.commit();\n    assertThat(cache.get(\"a\")).isNull();\n  }\n\n  /** @see <a href=\"https://github.com/JakeWharton/DiskLruCache/issues/2\">Issue #2</a> */\n  @Test public void aggressiveClearingHandlesRead() throws Exception {\n    deleteDirectory(cacheDir);\n    assertThat(cache.get(\"a\")).isNull();\n  }\n\n  private void assertJournalEquals(String... expectedBodyLines) throws Exception {\n    List<String> expectedLines = new ArrayList<String>();\n    expectedLines.add(MAGIC);\n    expectedLines.add(VERSION_1);\n    expectedLines.add(\"100\");\n    expectedLines.add(\"2\");\n    expectedLines.add(\"\");\n    expectedLines.addAll(Arrays.asList(expectedBodyLines));\n    assertThat(readJournalLines()).isEqualTo(expectedLines);\n  }\n\n  private void createJournal(String... bodyLines) throws Exception {\n    createJournalWithHeader(MAGIC, VERSION_1, \"100\", \"2\", \"\", bodyLines);\n  }\n\n  private void createJournalWithHeader(String magic, String version, String appVersion,\n      String valueCount, String blank, String... bodyLines) throws Exception {\n    Writer writer = new FileWriter(journalFile);\n    writer.write(magic + \"\\n\");\n    writer.write(version + \"\\n\");\n    writer.write(appVersion + \"\\n\");\n    writer.write(valueCount + \"\\n\");\n    writer.write(blank + \"\\n\");\n    for (String line : bodyLines) {\n      writer.write(line);\n      writer.write('\\n');\n    }\n    writer.close();\n  }\n\n  private List<String> readJournalLines() throws Exception {\n    List<String> result = new ArrayList<String>();\n    BufferedReader reader = new BufferedReader(new FileReader(journalFile));\n    String line;\n    while ((line = reader.readLine()) != null) {\n      result.add(line);\n    }\n    reader.close();\n    return result;\n  }\n\n  private File getCleanFile(String key, int index) {\n    return new File(cacheDir, key + \".\" + index);\n  }\n\n  private File getDirtyFile(String key, int index) {\n    return new File(cacheDir, key + \".\" + index + \".tmp\");\n  }\n\n  private static String readFile(File file) throws Exception {\n    Reader reader = new FileReader(file);\n    StringWriter writer = new StringWriter();\n    char[] buffer = new char[1024];\n    int count;\n    while ((count = reader.read(buffer)) != -1) {\n      writer.write(buffer, 0, count);\n    }\n    reader.close();\n    return writer.toString();\n  }\n\n  public static void writeFile(File file, String content) throws Exception {\n    FileWriter writer = new FileWriter(file);\n    writer.write(content);\n    writer.close();\n  }\n\n  private static void assertInoperable(DiskLruCache.Editor editor) throws Exception {\n    try {\n      editor.getString(0);\n      Assert.fail();\n    } catch (IllegalStateException expected) {\n    }\n    try {\n      editor.set(0, \"A\");\n      Assert.fail();\n    } catch (IllegalStateException expected) {\n    }\n    try {\n      editor.getFile(0);\n      Assert.fail();\n    } catch (IllegalStateException expected) {\n    }\n    try {\n      editor.commit();\n      Assert.fail();\n    } catch (IllegalStateException expected) {\n    }\n    try {\n      editor.abort();\n      Assert.fail();\n    } catch (IllegalStateException expected) {\n    }\n  }\n\n  private void generateSomeGarbageFiles() throws Exception {\n    File dir1 = new File(cacheDir, \"dir1\");\n    File dir2 = new File(dir1, \"dir2\");\n    writeFile(getCleanFile(\"g1\", 0), \"A\");\n    writeFile(getCleanFile(\"g1\", 1), \"B\");\n    writeFile(getCleanFile(\"g2\", 0), \"C\");\n    writeFile(getCleanFile(\"g2\", 1), \"D\");\n    writeFile(getCleanFile(\"g2\", 1), \"D\");\n    writeFile(new File(cacheDir, \"otherFile0\"), \"E\");\n    dir1.mkdir();\n    dir2.mkdir();\n    writeFile(new File(dir2, \"otherFile1\"), \"F\");\n  }\n\n  private void assertGarbageFilesAllDeleted() throws Exception {\n    FileSubject.assertThat(getCleanFile(\"g1\", 0)).doesNotExist();\n    FileSubject.assertThat(getCleanFile(\"g1\", 1)).doesNotExist();\n    FileSubject.assertThat(getCleanFile(\"g2\", 0)).doesNotExist();\n    FileSubject.assertThat(getCleanFile(\"g2\", 1)).doesNotExist();\n    FileSubject.assertThat(new File(cacheDir, \"otherFile0\")).doesNotExist();\n    FileSubject.assertThat(new File(cacheDir, \"dir1\")).doesNotExist();\n  }\n\n  private void set(String key, String value0, String value1) throws Exception {\n    DiskLruCache.Editor editor = cache.edit(key);\n    editor.set(0, value0);\n    editor.set(1, value1);\n    editor.commit();\n  }\n\n  private void assertAbsent(String key) throws Exception {\n    DiskLruCache.Value value = cache.get(key);\n    if (value != null) {\n      Assert.fail();\n    }\n    FileSubject.assertThat(getCleanFile(key, 0)).doesNotExist();\n    FileSubject.assertThat(getCleanFile(key, 1)).doesNotExist();\n    FileSubject.assertThat(getDirtyFile(key, 0)).doesNotExist();\n    FileSubject.assertThat(getDirtyFile(key, 1)).doesNotExist();\n  }\n\n  private void assertValue(String key, String value0, String value1) throws Exception {\n    DiskLruCache.Value value = cache.get(key);\n    assertThat(value.getString(0)).isEqualTo(value0);\n    assertThat(value.getLength(0)).isEqualTo(value0.length());\n    assertThat(value.getString(1)).isEqualTo(value1);\n    assertThat(value.getLength(1)).isEqualTo(value1.length());\n    FileSubject.assertThat(getCleanFile(key, 0)).exists();\n    FileSubject.assertThat(getCleanFile(key, 1)).exists();\n  }\n\n  private static void deleteDirectory(File file) {\n    if (file.isDirectory()) {\n      File[] children = file.listFiles();\n      if (children != null && children.length > 0) {\n        for (File child : children) {\n          deleteDirectory(child);\n        }\n      }\n    }\n    assertThat(!file.exists() || file.delete()).isTrue();\n  }\n\n  // TODO(b/134664588): Remove after go/truth-subject-lsc\n  @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n  static final class FileSubject extends ComparableSubject {\n    private static final Subject.Factory<FileSubject, File> FACTORY =\n        new Subject.Factory<FileSubject, File>() {\n          @Override\n          public FileSubject createSubject(FailureMetadata metadata, File actual) {\n            return new FileSubject(metadata, actual);\n          }\n        };\n    private final File actual;\n\n    static FileSubject assertThat(File file) {\n      return Truth.assertAbout(FACTORY).that(file);\n    }\n\n    protected FileSubject(FailureMetadata metadata, File actual) {\n      super(metadata, actual);\n      this.actual = actual;\n    }\n\n    public void doesNotExist() {\n      if (actual.exists()) {\n        failWithActual(simpleFact(\"expected to not exist\"));\n      }\n    }\n\n    public void exists() {\n      if (!actual.exists()) {\n        failWithActual(simpleFact(\"expected to exist\"));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "third_party/disklrucache/src/test/java/com/bumptech/glide/disklrucache/StrictLineReaderTest.java",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\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.bumptech.glide.disklrucache;\n\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n@RunWith(JUnit4.class)\npublic class StrictLineReaderTest {\n  @Test public void lineReaderConsistencyWithReadAsciiLine() {\n    try {\n      // Testing with LineReader buffer capacity 32 to check some corner cases.\n      StrictLineReader lineReader =\n          new StrictLineReader(createTestInputStream(), 32, Util.US_ASCII);\n      InputStream refStream = createTestInputStream();\n      while (true) {\n        try {\n          String refLine = readAsciiLine(refStream);\n          try {\n            String line = lineReader.readLine();\n            if (!refLine.equals(line)) {\n              Assert.fail(\"line (\\\"\" + line + \"\\\") differs from expected (\\\"\" + refLine + \"\\\").\");\n            }\n          } catch (EOFException eof) {\n            Assert.fail(\"line reader threw EOFException too early.\");\n          }\n        } catch (EOFException refEof) {\n          try {\n            lineReader.readLine();\n            Assert.fail(\"line reader didn't throw the expected EOFException.\");\n          } catch (EOFException expected) {\n            break;\n          }\n        }\n      }\n      refStream.close();\n      lineReader.close();\n    } catch (IOException ioe) {\n      Assert.fail(\"Unexpected IOException \" + ioe.toString());\n    }\n  }\n\n  /* XXX From libcore.io.Streams */\n  private static String readAsciiLine(InputStream in) throws IOException {\n    // TODO: support UTF-8 here instead\n\n    StringBuilder result = new StringBuilder(80);\n    while (true) {\n      int c = in.read();\n      if (c == -1) {\n        throw new EOFException();\n      } else if (c == '\\n') {\n        break;\n      }\n\n      result.append((char) c);\n    }\n    int length = result.length();\n    if (length > 0 && result.charAt(length - 1) == '\\r') {\n      result.setLength(length - 1);\n    }\n    return result.toString();\n  }\n\n  private static InputStream createTestInputStream() {\n    return new ByteArrayInputStream((\"\"\n        // Each source lines below should represent 32 bytes, until the next comment.\n        + \"12 byte line\\n18 byte line......\\n\"\n        + \"pad\\nline spanning two 32-byte bu\"\n        + \"ffers\\npad......................\\n\"\n        + \"pad\\nline spanning three 32-byte \"\n        + \"buffers and ending with LF at th\"\n        + \"e end of a 32 byte buffer......\\n\"\n        + \"pad\\nLine ending with CRLF split\"\n        + \" at the end of a 32-byte buffer\\r\"\n        + \"\\npad...........................\\n\"\n        // End of 32-byte lines.\n        + \"line ending with CRLF\\r\\n\"\n        + \"this is a long line with embedded CR \\r ending with CRLF and having more than \"\n        + \"32 characters\\r\\n\"\n        + \"unterminated line - should be dropped\").getBytes());\n  }\n}\n\n"
  },
  {
    "path": "third_party/gif_decoder/LICENSE",
    "content": "Copyright (c) 2013 Xcellent Creations, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "third_party/gif_decoder/THIRD_PARTY.md",
    "content": "URL: https://gist.githubusercontent.com/devunwired/4479231/raw/df2725be4ae0f12f5265deaf0a769936ea94950b/GifDecoder.java\nVersion: df2725be4ae0f12f5265deaf0a769936ea94950b\nLicense: MIT\nLicense File: LICENSE\n\nDescription:\nImplementation of GifDecoder that is more memory efficient to animate for \nAndroid devices. This implementation does not house in memory a Bitmap for \nevery image frame. Images are instead decoded on-the-fly, and only the minimum \ndata to create the next frame in the sequence is kept. The implementation has \nalso been adapted to reduce memory allocations in the decoding process to \nreduce time to render each frame.\n\nAdapted from:\nhttp://show.docjava.com/book/cgij/exportToHTML/ip/gif/stills/GifDecoder.java.html\n\nLocal Modifications:\nBroke headers and frames out into separate files and added ability to share\nheaders between multiple decoders. Added interface for reusing bitmaps each\nframe. Bugfixes.\n"
  },
  {
    "path": "third_party/gif_decoder/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nandroid {\n    namespace = \"com.bumptech.glide.gifdecoder\"\n    compileSdkVersion = libs.versions.compile.sdk.version.get()\n\n    defaultConfig {\n        minSdk = libs.versions.min.sdk.version.get().toInt()\n    }\n}\n\ndependencies {\n    implementation(libs.androidx.annotation)\n\n    testImplementation(project(\":testutil\"))\n    testImplementation(libs.androidx.annotation)\n    testImplementation(libs.truth)\n    testImplementation(libs.junit)\n    testImplementation(libs.mockito.core)\n    testImplementation(libs.robolectric)\n}\n\napply(from = \"${rootProject.projectDir}/scripts/upload.gradle.kts\")"
  },
  {
    "path": "third_party/gif_decoder/gradle.properties",
    "content": "POM_NAME=Glide GIF Decoder Library\nPOM_ARTIFACT_ID=gifdecoder\nPOM_PACKAGING=aar\nPOM_DESCRIPTION=Implementation of GifDecoder that is more memory efficient to animate for Android devices.\n"
  },
  {
    "path": "third_party/gif_decoder/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\"/>\n</lint>\n"
  },
  {
    "path": "third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java",
    "content": "package com.bumptech.glide.gifdecoder;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.IntDef;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.io.InputStream;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.nio.ByteBuffer;\n\n/**\n * Shared interface for GIF decoders.\n */\npublic interface GifDecoder {\n\n  /** File read status: No errors. */\n  int STATUS_OK = 0;\n  /** File read status: Error decoding file (may be partially decoded). */\n  int STATUS_FORMAT_ERROR = 1;\n  /** File read status: Unable to open source. */\n  int STATUS_OPEN_ERROR = 2;\n  /** Unable to fully decode the current frame. */\n  int STATUS_PARTIAL_DECODE = 3;\n  /** The total iteration count which means repeat forever. */\n  int TOTAL_ITERATION_COUNT_FOREVER = 0;\n\n  /** Android Lint annotation for status codes that can be used with a GIF decoder. */\n  @Retention(RetentionPolicy.SOURCE)\n  @IntDef(value = {STATUS_OK, STATUS_FORMAT_ERROR, STATUS_OPEN_ERROR, STATUS_PARTIAL_DECODE})\n  @interface GifDecodeStatus {\n  }\n\n  /**\n   * An interface that can be used to provide reused {@link android.graphics.Bitmap}s to avoid GCs\n   * from constantly allocating {@link android.graphics.Bitmap}s for every frame.\n   */\n  interface BitmapProvider {\n    /**\n     * Returns an {@link Bitmap} with exactly the given dimensions and config.\n     *\n     * @param width  The width in pixels of the desired {@link android.graphics.Bitmap}.\n     * @param height The height in pixels of the desired {@link android.graphics.Bitmap}.\n     * @param config The {@link android.graphics.Bitmap.Config} of the desired {@link\n     *               android.graphics.Bitmap}.\n     */\n    @NonNull\n    Bitmap obtain(int width, int height, @NonNull Bitmap.Config config);\n\n    /**\n     * Releases the given Bitmap back to the pool.\n     */\n    void release(@NonNull Bitmap bitmap);\n\n    /**\n     * Returns a byte array used for decoding and generating the frame bitmap.\n     *\n     * @param size the size of the byte array to obtain\n     */\n    @NonNull\n    byte[] obtainByteArray(int size);\n\n    /**\n     * Releases the given byte array back to the pool.\n     */\n    void release(@NonNull byte[] bytes);\n\n    /**\n     * Returns an int array used for decoding/generating the frame bitmaps.\n     */\n    @NonNull\n    int[] obtainIntArray(int size);\n\n    /**\n     * Release the given array back to the pool.\n     */\n    void release(@NonNull int[] array);\n  }\n\n  int getWidth();\n\n  int getHeight();\n\n  @NonNull\n  ByteBuffer getData();\n\n  /**\n   * Returns the current status of the decoder.\n   *\n   * <p> Status will update per frame to allow the caller to tell whether or not the current frame\n   * was decoded successfully and/or completely. Format and open failures persist across frames.\n   * </p>\n   */\n  @GifDecodeStatus\n  int getStatus();\n\n  /**\n   * Move the animation frame counter forward.\n   */\n  void advance();\n\n  /**\n   * Gets display duration for specified frame.\n   *\n   * @param n int index of frame.\n   * @return delay in milliseconds.\n   */\n  int getDelay(int n);\n\n  /**\n   * Gets display duration for the upcoming frame in ms.\n   */\n  int getNextDelay();\n\n  /**\n   * Gets the number of frames read from file.\n   *\n   * @return frame count.\n   */\n  int getFrameCount();\n\n  /**\n   * Gets the current index of the animation frame, or -1 if animation hasn't not yet started.\n   *\n   * @return frame index.\n   */\n  int getCurrentFrameIndex();\n\n  /**\n   * Resets the frame pointer to before the 0th frame, as if we'd never used this decoder to\n   * decode any frames.\n   */\n  void resetFrameIndex();\n\n  /**\n   * Gets the \"Netscape\" loop count, if any. A count of 0 means repeat indefinitely.\n   *\n   * @deprecated Use {@link #getNetscapeLoopCount()} instead.\n   *             This method cannot distinguish whether the loop count is 1 or doesn't exist.\n   * @return loop count if one was specified, else 1.\n   */\n  @Deprecated\n  int getLoopCount();\n\n  /**\n   * Gets the \"Netscape\" loop count, if any.\n   * A count of 0 ({@link GifHeader#NETSCAPE_LOOP_COUNT_FOREVER}) means repeat indefinitely.\n   * It must not be a negative value.\n   * <br>\n   * Use {@link #getTotalIterationCount()}\n   * to know how many times the animation sequence should be displayed.\n   *\n   * @return loop count if one was specified,\n   *         else -1 ({@link GifHeader#NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST}).\n   */\n  int getNetscapeLoopCount();\n\n  /**\n   * Gets the total count\n   * which represents how many times the animation sequence should be displayed.\n   * A count of 0 ({@link #TOTAL_ITERATION_COUNT_FOREVER}) means repeat indefinitely.\n   * It must not be a negative value.\n   * <p>\n   *     The total count is calculated as follows by using {@link #getNetscapeLoopCount()}.\n   *     This behavior is the same as most web browsers.\n   *     <table border='1'>\n   *         <tr class='tableSubHeadingColor'><th>{@code getNetscapeLoopCount()}</th>\n   *             <th>The total count</th></tr>\n   *         <tr><td>{@link GifHeader#NETSCAPE_LOOP_COUNT_FOREVER}</td>\n   *             <td>{@link #TOTAL_ITERATION_COUNT_FOREVER}</td></tr>\n   *         <tr><td>{@link GifHeader#NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST}</td>\n   *             <td>{@code 1}</td></tr>\n   *         <tr><td>{@code n (n > 0)}</td>\n   *             <td>{@code n + 1}</td></tr>\n   *     </table>\n   * </p>\n   *\n   * @see <a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=592735#c5\">Discussion about\n   *      the iteration count of animated GIFs (Chromium Issue 592735)</a>\n   *\n   * @return total iteration count calculated from \"Netscape\" loop count.\n   */\n  int getTotalIterationCount();\n\n  /**\n   * Returns an estimated byte size for this decoder based on the data provided to {@link\n   * #setData(GifHeader, byte[])}, as well as internal buffers.\n   */\n  int getByteSize();\n\n  /**\n   * Get the next frame in the animation sequence.\n   *\n   * @return Bitmap representation of frame.\n   */\n  @Nullable\n  Bitmap getNextFrame();\n\n  /**\n   * Reads GIF image from stream.\n   *\n   * @param is containing GIF file.\n   * @return read status code (0 = no errors).\n   */\n  @GifDecodeStatus\n  int read(@Nullable InputStream is, int contentLength);\n\n  void clear();\n\n  void setData(@NonNull GifHeader header, @NonNull byte[] data);\n\n  void setData(@NonNull GifHeader header, @NonNull ByteBuffer buffer);\n\n  void setData(@NonNull GifHeader header, @NonNull ByteBuffer buffer, int sampleSize);\n\n  /**\n   * Reads GIF image from byte array.\n   *\n   * @param data containing GIF file.\n   * @return read status code (0 = no errors).\n   */\n  @GifDecodeStatus\n  int read(@Nullable byte[] data);\n\n\n  /**\n   * Sets the default {@link android.graphics.Bitmap.Config} to use when decoding frames of a GIF.\n   *\n   * <p>Valid options are {@link android.graphics.Bitmap.Config#ARGB_8888} and\n   * {@link android.graphics.Bitmap.Config#RGB_565}.\n   * {@link android.graphics.Bitmap.Config#ARGB_8888} will produce higher quality frames, but will\n   * also use 2x the memory of {@link android.graphics.Bitmap.Config#RGB_565}.\n   *\n   * <p>Defaults to {@link android.graphics.Bitmap.Config#ARGB_8888}\n   *\n   * <p>This value is not a guarantee. For example if set to\n   * {@link android.graphics.Bitmap.Config#RGB_565} and the GIF contains transparent pixels,\n   * {@link android.graphics.Bitmap.Config#ARGB_8888} will be used anyway to support the\n   * transparency.\n   */\n  void setDefaultBitmapConfig(@NonNull Bitmap.Config format);\n}\n"
  },
  {
    "path": "third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java",
    "content": "package com.bumptech.glide.gifdecoder;\n\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.IntDef;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n/**\n * Inner model class housing metadata for each frame.\n *\n * @see <a href=\"https://www.w3.org/Graphics/GIF/spec-gif89a.txt\">GIF 89a Specification</a>\n */\nclass GifFrame {\n  /**\n   * GIF Disposal Method meaning take no action.\n   * <p><b>GIF89a</b>: <i>No disposal specified.\n   * The decoder is not required to take any action.</i></p>\n   */\n  static final int DISPOSAL_UNSPECIFIED = 0;\n  /**\n   * GIF Disposal Method meaning leave canvas from previous frame.\n   * <p><b>GIF89a</b>: <i>Do not dispose.\n   * The graphic is to be left in place.</i></p>\n   */\n  static final int DISPOSAL_NONE = 1;\n  /**\n   * GIF Disposal Method meaning clear canvas to background color.\n   * <p><b>GIF89a</b>: <i>Restore to background color.\n   * The area used by the graphic must be restored to the background color.</i></p>\n   */\n  static final int DISPOSAL_BACKGROUND = 2;\n  /**\n   * GIF Disposal Method meaning clear canvas to frame before last.\n   * <p><b>GIF89a</b>: <i>Restore to previous.\n   * The decoder is required to restore the area overwritten by the graphic\n   * with what was there prior to rendering the graphic.</i></p>\n   */\n  static final int DISPOSAL_PREVIOUS = 3;\n\n  /**\n   * <p><b>GIF89a</b>:\n   * <i>Indicates the way in which the graphic is to be treated after being displayed.</i></p>\n   * Disposal methods 0-3 are defined, 4-7 are reserved for future use.\n   *\n   * @see #DISPOSAL_UNSPECIFIED\n   * @see #DISPOSAL_NONE\n   * @see #DISPOSAL_BACKGROUND\n   * @see #DISPOSAL_PREVIOUS\n   */\n  @Retention(RetentionPolicy.SOURCE)\n  @IntDef(value = {DISPOSAL_UNSPECIFIED, DISPOSAL_NONE, DISPOSAL_BACKGROUND, DISPOSAL_PREVIOUS})\n  private @interface GifDisposalMethod {\n  }\n\n  int ix, iy, iw, ih;\n  /**\n   * Control Flag.\n   */\n  boolean interlace;\n  /**\n   * Control Flag.\n   */\n  boolean transparency;\n  /**\n   * Disposal Method.\n   */\n  @GifDisposalMethod\n  int dispose;\n  /**\n   * Transparency Index.\n   */\n  int transIndex;\n  /**\n   * Delay, in milliseconds, to next frame.\n   */\n  int delay;\n  /**\n   * Index in the raw buffer where we need to start reading to decode.\n   */\n  int bufferFrameStart;\n  /**\n   * Local Color Table.\n   */\n  @ColorInt\n  int[] lct;\n}\n"
  },
  {
    "path": "third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java",
    "content": "package com.bumptech.glide.gifdecoder;\n\nimport androidx.annotation.ColorInt;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A header object containing the number of frames in an animated GIF image as well as basic\n * metadata like width and height that can be used to decode each individual frame of the GIF. Can\n * be shared by one or more {@link com.bumptech.glide.gifdecoder.GifDecoder}s to play the same\n * animated GIF in multiple views.\n *\n * @see <a href=\"https://www.w3.org/Graphics/GIF/spec-gif89a.txt\">GIF 89a Specification</a>\n */\npublic class GifHeader {\n\n  /** The \"Netscape\" loop count which means loop forever. */\n  public static final int NETSCAPE_LOOP_COUNT_FOREVER = 0;\n  /** Indicates that this header has no \"Netscape\" loop count. */\n  public static final int NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST = -1;\n\n  @ColorInt\n  int[] gct = null;\n  @GifDecoder.GifDecodeStatus\n  int status = GifDecoder.STATUS_OK;\n  int frameCount = 0;\n\n  GifFrame currentFrame;\n  final List<GifFrame> frames = new ArrayList<>();\n  /** Logical screen size: Full image width. */\n  int width;\n  /** Logical screen size: Full image height. */\n  int height;\n\n  // 1 : global color table flag.\n  boolean gctFlag;\n  /**\n   * Size of Global Color Table.\n   * The value is already computed to be a regular number, this field doesn't store the exponent.\n   */\n  int gctSize;\n  /** Background color index into the Global/Local color table. */\n  int bgIndex;\n  /**\n   * Pixel aspect ratio.\n   * Factor used to compute an approximation of the aspect ratio of the pixel in the original image.\n   */\n  int pixelAspect;\n  @ColorInt\n  int bgColor;\n  int loopCount = NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST;\n\n  public int getHeight() {\n    return height;\n  }\n\n  public int getWidth() {\n    return width;\n  }\n\n  public int getNumFrames() {\n    return frameCount;\n  }\n\n  /**\n   * Global status code of GIF data parsing.\n   */\n  @GifDecoder.GifDecodeStatus\n  public int getStatus() {\n    return status;\n  }\n}\n"
  },
  {
    "path": "third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java",
    "content": "package com.bumptech.glide.gifdecoder;\n\nimport static com.bumptech.glide.gifdecoder.GifDecoder.STATUS_FORMAT_ERROR;\nimport static com.bumptech.glide.gifdecoder.GifFrame.DISPOSAL_NONE;\nimport static com.bumptech.glide.gifdecoder.GifFrame.DISPOSAL_UNSPECIFIED;\n\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.nio.BufferUnderflowException;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.util.Arrays;\n\n/**\n * A class responsible for creating {@link com.bumptech.glide.gifdecoder.GifHeader}s from data\n * representing animated GIFs.\n *\n * @see <a href=\"https://www.w3.org/Graphics/GIF/spec-gif89a.txt\">GIF 89a Specification</a>\n */\npublic class GifHeaderParser {\n  private static final String TAG = \"GifHeaderParser\";\n\n  private static final int MASK_INT_LOWEST_BYTE = 0x000000FF;\n\n  /** Identifies the beginning of an Image Descriptor. */\n  private static final int IMAGE_SEPARATOR = 0x2C;\n  /** Identifies the beginning of an extension block. */\n  private static final int EXTENSION_INTRODUCER = 0x21;\n  /** This block is a single-field block indicating the end of the GIF Data Stream. */\n  private static final int TRAILER = 0x3B;\n  // Possible labels that identify the current extension block.\n  private static final int LABEL_GRAPHIC_CONTROL_EXTENSION = 0xF9;\n  private static final int LABEL_APPLICATION_EXTENSION = 0xFF;\n  private static final int LABEL_COMMENT_EXTENSION = 0xFE;\n  private static final int LABEL_PLAIN_TEXT_EXTENSION = 0x01;\n\n  // Graphic Control Extension packed field masks\n\n  /**\n   * Mask (bits 4-2) to extract Disposal Method of the current frame.\n   *\n   * @see GifFrame.GifDisposalMethod possible values\n   */\n  private static final int GCE_MASK_DISPOSAL_METHOD = 0b00011100;\n  /**\n   * Shift so the Disposal Method extracted from the packed value is on the least significant bit.\n   */\n  private static final int GCE_DISPOSAL_METHOD_SHIFT = 2;\n  /**\n   * Mask (bit 0) to extract Transparent Color Flag of the current frame.\n   * <p><b>GIF89a</b>: <i>Indicates whether a transparency index is given\n   * in the Transparent Index field.</i></p>\n   * Possible values are:<ul>\n   * <li>0 - Transparent Index is not given.</li>\n   * <li>1 - Transparent Index is given.</li>\n   * </ul>\n   */\n  private static final int GCE_MASK_TRANSPARENT_COLOR_FLAG = 0b00000001;\n\n  // Image Descriptor packed field masks (describing Local Color Table)\n\n  /**\n   * Mask (bit 7) to extract Local Color Table Flag of the current image.\n   * <p><b>GIF89a</b>: <i>Indicates the presence of a Local Color Table\n   * immediately following this Image Descriptor.</i></p>\n   */\n  private static final int DESCRIPTOR_MASK_LCT_FLAG = 0b10000000;\n  /**\n   * Mask (bit 6) to extract Interlace Flag of the current image.\n   * <p><b>GIF89a</b>: <i>Indicates if the image is interlaced.\n   * An image is interlaced in a four-pass interlace pattern.</i></p>\n   * Possible values are:<ul>\n   * <li>0 - Image is not interlaced.</li>\n   * <li>1 - Image is interlaced.</li>\n   * </ul>\n   */\n  private static final int DESCRIPTOR_MASK_INTERLACE_FLAG = 0b01000000;\n  /**\n   * Mask (bits 2-0) to extract Size of the Local Color Table of the current image.\n   * <p><b>GIF89a</b>: <i>If the Local Color Table Flag is set to 1, the value in this\n   * field is used to calculate the number of bytes contained in the Local Color Table.\n   * To determine that actual size of the color table, raise 2 to [the value of the field + 1].\n   * This value should be 0 if there is no Local Color Table specified.</i></p>\n   */\n  private static final int DESCRIPTOR_MASK_LCT_SIZE = 0b00000111;\n\n  // Logical Screen Descriptor packed field masks (describing Global Color Table)\n\n  /**\n   * Mask (bit 7) to extract Global Color Table Flag of the current image.\n   * <p><b>GIF89a</b>: <i>Indicates the presence of a Global Color Table\n   * immediately following this Image Descriptor.</i></p>\n   * Possible values are:<ul>\n   * <li>0 - No Global Color Table follows, the Background Color Index field is meaningless.</li>\n   * <li>1 - A Global Color Table will immediately follow,\n   * the Background Color Index field is meaningful.</li>\n   * </ul>\n   */\n  private static final int LSD_MASK_GCT_FLAG = 0b10000000;\n  /**\n   * Mask (bits 2-0) to extract Size of the Global Color Table of the current image.\n   * <p><b>GIF89a</b>: <i>If the Global Color Table Flag is set to 1, the value in this\n   * field is used to calculate the number of bytes contained in the Global Color Table.\n   * To determine that actual size of the color table, raise 2 to [the value of the field + 1].\n   * Even if there is no Global Color Table specified, set this field according to the above\n   * formula so that decoders can choose the best graphics mode to display the stream in.</i></p>\n   */\n  private static final int LSD_MASK_GCT_SIZE = 0b00000111;\n\n  /** The minimum frame delay in hundredths of a second. */\n  static final int MIN_FRAME_DELAY = 2;\n  /**\n   * The default frame delay in hundredths of a second.\n   * This is used for GIFs with frame delays less than the minimum.\n   */\n  static final int DEFAULT_FRAME_DELAY = 10;\n\n  private static final int MAX_BLOCK_SIZE = 256;\n  // Raw data read working array.\n  private final byte[] block = new byte[MAX_BLOCK_SIZE];\n\n  private ByteBuffer rawData;\n  private GifHeader header;\n  private int blockSize = 0;\n\n  public GifHeaderParser setData(@NonNull ByteBuffer data) {\n    reset();\n    rawData = data.asReadOnlyBuffer();\n    rawData.position(0);\n    rawData.order(ByteOrder.LITTLE_ENDIAN);\n    return this;\n  }\n\n  public GifHeaderParser setData(@Nullable byte[] data) {\n    if (data != null) {\n      setData(ByteBuffer.wrap(data));\n    } else {\n      rawData = null;\n      header.status = GifDecoder.STATUS_OPEN_ERROR;\n    }\n    return this;\n  }\n\n  public void clear() {\n    rawData = null;\n    header = null;\n  }\n\n  private void reset() {\n    rawData = null;\n    Arrays.fill(block, (byte) 0);\n    header = new GifHeader();\n    blockSize = 0;\n  }\n\n  @NonNull\n  public GifHeader parseHeader() {\n    if (rawData == null) {\n      throw new IllegalStateException(\"You must call setData() before parseHeader()\");\n    }\n    if (err()) {\n      return header;\n    }\n\n    readHeader();\n    if (!err()) {\n      readContents();\n      if (header.frameCount < 0) {\n        header.status = STATUS_FORMAT_ERROR;\n      }\n    }\n\n    return header;\n  }\n\n  /**\n   * Determines if the GIF is animated by trying to read in the first 2 frames\n   * This method re-parses the data even if the header has already been read.\n   */\n  public boolean isAnimated() {\n    readHeader();\n    if (!err()) {\n      readContents(2 /* maxFrames */);\n    }\n    return header.frameCount > 1;\n  }\n\n  /**\n   * Main file parser. Reads GIF content blocks.\n   */\n  private void readContents() {\n    readContents(Integer.MAX_VALUE /* maxFrames */);\n  }\n\n  /**\n   * Main file parser. Reads GIF content blocks. Stops after reading maxFrames\n   */\n  private void readContents(int maxFrames) {\n    // Read GIF file content blocks.\n    boolean done = false;\n    while (!(done || err() || header.frameCount > maxFrames)) {\n      int code = read();\n      switch (code) {\n        case IMAGE_SEPARATOR:\n          // The Graphic Control Extension is optional, but will always come first if it exists.\n          // If one did exist, there will be a non-null current frame which we should use.\n          // However if one did not exist, the current frame will be null\n          // and we must create it here. See issue #134.\n          if (header.currentFrame == null) {\n            header.currentFrame = new GifFrame();\n          }\n          readBitmap();\n          break;\n        case EXTENSION_INTRODUCER:\n          int extensionLabel = read();\n          switch (extensionLabel) {\n            case LABEL_GRAPHIC_CONTROL_EXTENSION:\n              // Start a new frame.\n              header.currentFrame = new GifFrame();\n              readGraphicControlExt();\n              break;\n            case LABEL_APPLICATION_EXTENSION:\n              readBlock();\n              StringBuilder app = new StringBuilder();\n              for (int i = 0; i < 11; i++) {\n                app.append((char) block[i]);\n              }\n              if (app.toString().equals(\"NETSCAPE2.0\")) {\n                readNetscapeExt();\n              } else {\n                // Don't care.\n                skip();\n              }\n              break;\n            case LABEL_COMMENT_EXTENSION:\n              skip();\n              break;\n            case LABEL_PLAIN_TEXT_EXTENSION:\n              skip();\n              break;\n            default:\n              // Uninteresting extension.\n              skip();\n          }\n          break;\n        case TRAILER:\n          // This block is a single-field block indicating the end of the GIF Data Stream.\n          done = true;\n          break;\n        // Bad byte, but keep going and see what happens\n        case 0x00:\n        default:\n          header.status = STATUS_FORMAT_ERROR;\n      }\n    }\n  }\n\n  /**\n   * Reads Graphic Control Extension values.\n   */\n  private void readGraphicControlExt() {\n    // Block size.\n    read();\n    /*\n     * Graphic Control Extension packed field:\n     *      7 6 5 4 3 2 1 0\n     *     +---------------+\n     *  1  |     |     | | |\n     *\n     * Reserved                    3 Bits\n     * Disposal Method             3 Bits\n     * User Input Flag             1 Bit\n     * Transparent Color Flag      1 Bit\n     */\n    int packed = read();\n    // Disposal method.\n    //noinspection WrongConstant field has to be extracted from packed value\n    header.currentFrame.dispose = (packed & GCE_MASK_DISPOSAL_METHOD) >> GCE_DISPOSAL_METHOD_SHIFT;\n    if (header.currentFrame.dispose == DISPOSAL_UNSPECIFIED) {\n      // Elect to keep old image if discretionary.\n      header.currentFrame.dispose = DISPOSAL_NONE;\n    }\n    header.currentFrame.transparency = (packed & GCE_MASK_TRANSPARENT_COLOR_FLAG) != 0;\n    // Delay in milliseconds.\n    int delayInHundredthsOfASecond = readShort();\n    // TODO: consider allowing -1 to indicate show forever.\n    if (delayInHundredthsOfASecond < MIN_FRAME_DELAY) {\n      delayInHundredthsOfASecond = DEFAULT_FRAME_DELAY;\n    }\n    header.currentFrame.delay = delayInHundredthsOfASecond * 10;\n    // Transparent color index\n    header.currentFrame.transIndex = read();\n    // Block terminator\n    read();\n  }\n\n  /**\n   * Reads next frame image.\n   */\n  private void readBitmap() {\n    // (sub)image position & size.\n    header.currentFrame.ix = readShort();\n    header.currentFrame.iy = readShort();\n    header.currentFrame.iw = readShort();\n    header.currentFrame.ih = readShort();\n\n    /*\n     * Image Descriptor packed field:\n     *     7 6 5 4 3 2 1 0\n     *    +---------------+\n     * 9  | | | |   |     |\n     *\n     * Local Color Table Flag     1 Bit\n     * Interlace Flag             1 Bit\n     * Sort Flag                  1 Bit\n     * Reserved                   2 Bits\n     * Size of Local Color Table  3 Bits\n     */\n    int packed = read();\n    boolean lctFlag = (packed & DESCRIPTOR_MASK_LCT_FLAG) != 0;\n    int lctSize = (int) Math.pow(2, (packed & DESCRIPTOR_MASK_LCT_SIZE) + 1);\n    header.currentFrame.interlace = (packed & DESCRIPTOR_MASK_INTERLACE_FLAG) != 0;\n    if (lctFlag) {\n      header.currentFrame.lct = readColorTable(lctSize);\n    } else {\n      // No local color table.\n      header.currentFrame.lct = null;\n    }\n\n    // Save this as the decoding position pointer.\n    header.currentFrame.bufferFrameStart = rawData.position();\n\n    // False decode pixel data to advance buffer.\n    skipImageData();\n\n    if (err()) {\n      return;\n    }\n\n    header.frameCount++;\n    // Add image to frame.\n    header.frames.add(header.currentFrame);\n  }\n\n  /**\n   * Reads Netscape extension to obtain iteration count.\n   */\n  private void readNetscapeExt() {\n    do {\n      readBlock();\n      if (block[0] == 1) {\n        // Loop count sub-block.\n        int b1 = ((int) block[1]) & MASK_INT_LOWEST_BYTE;\n        int b2 = ((int) block[2]) & MASK_INT_LOWEST_BYTE;\n        header.loopCount = (b2 << 8) | b1;\n      }\n    } while (blockSize > 0 && !err());\n  }\n\n\n  /**\n   * Reads GIF file header information.\n   */\n  private void readHeader() {\n    StringBuilder id = new StringBuilder();\n    for (int i = 0; i < 6; i++) {\n      id.append((char) read());\n    }\n    if (!id.toString().startsWith(\"GIF\")) {\n      header.status = STATUS_FORMAT_ERROR;\n      return;\n    }\n    readLSD();\n    if (header.gctFlag && !err()) {\n      header.gct = readColorTable(header.gctSize);\n      header.bgColor = header.gct[header.bgIndex];\n    }\n  }\n\n  /**\n   * Reads Logical Screen Descriptor.\n   */\n  private void readLSD() {\n    // Logical screen size.\n    header.width = readShort();\n    header.height = readShort();\n    /*\n     * Logical Screen Descriptor packed field:\n     *      7 6 5 4 3 2 1 0\n     *     +---------------+\n     *  4  | |     | |     |\n     *\n     * Global Color Table Flag     1 Bit\n     * Color Resolution            3 Bits\n     * Sort Flag                   1 Bit\n     * Size of Global Color Table  3 Bits\n     */\n    int packed = read();\n    header.gctFlag = (packed & LSD_MASK_GCT_FLAG) != 0;\n    header.gctSize = (int) Math.pow(2, (packed & LSD_MASK_GCT_SIZE) + 1);\n    // Background color index.\n    header.bgIndex = read();\n    // Pixel aspect ratio\n    header.pixelAspect = read();\n  }\n\n  /**\n   * Reads color table as 256 RGB integer values.\n   *\n   * @param nColors int number of colors to read.\n   * @return int array containing 256 colors (packed ARGB with full alpha).\n   */\n  @Nullable\n  private int[] readColorTable(int nColors) {\n    int nBytes = 3 * nColors;\n    int[] tab = null;\n    byte[] c = new byte[nBytes];\n\n    try {\n      rawData.get(c);\n\n      // TODO: what bounds checks are we avoiding if we know the number of colors?\n      // Max size to avoid bounds checks.\n      tab = new int[MAX_BLOCK_SIZE];\n      int i = 0;\n      int j = 0;\n      while (i < nColors) {\n        int r = ((int) c[j++]) & MASK_INT_LOWEST_BYTE;\n        int g = ((int) c[j++]) & MASK_INT_LOWEST_BYTE;\n        int b = ((int) c[j++]) & MASK_INT_LOWEST_BYTE;\n        tab[i++] = 0xFF000000 | (r << 16) | (g << 8) | b;\n      }\n    } catch (BufferUnderflowException e) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Format Error Reading Color Table\", e);\n      }\n      header.status = STATUS_FORMAT_ERROR;\n    }\n\n    return tab;\n  }\n\n  /**\n   * Skips LZW image data for a single frame to advance buffer.\n   */\n  private void skipImageData() {\n    // lzwMinCodeSize\n    read();\n    // data sub-blocks\n    skip();\n  }\n\n  /**\n   * Skips variable length blocks up to and including next zero length block.\n   */\n  private void skip() {\n    int blockSize;\n    do {\n      blockSize = read();\n      int newPosition = Math.min(rawData.position() + blockSize, rawData.limit());\n      rawData.position(newPosition);\n    } while (blockSize > 0);\n  }\n\n  /**\n   * Reads next variable length block from input.\n   */\n  private void readBlock() {\n    blockSize = read();\n    int n = 0;\n    if (blockSize > 0) {\n      int count = 0;\n      try {\n        while (n < blockSize) {\n          count = blockSize - n;\n          rawData.get(block, n, count);\n\n          n += count;\n        }\n      } catch (Exception e) {\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n          Log.d(TAG,\n              \"Error Reading Block n: \" + n + \" count: \" + count + \" blockSize: \" + blockSize, e);\n        }\n        header.status = STATUS_FORMAT_ERROR;\n      }\n    }\n  }\n\n  /**\n   * Reads a single byte from the input stream.\n   */\n  private int read() {\n    int currByte = 0;\n    try {\n      currByte = rawData.get() & MASK_INT_LOWEST_BYTE;\n    } catch (Exception e) {\n      header.status = STATUS_FORMAT_ERROR;\n    }\n    return currByte;\n  }\n\n  /**\n   * Reads next 16-bit value, LSB first.\n   */\n  private int readShort() {\n    // Read 16-bit value.\n    return rawData.getShort();\n  }\n\n  private boolean err() {\n    return header.status != GifDecoder.STATUS_OK;\n  }\n}\n"
  },
  {
    "path": "third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/StandardGifDecoder.java",
    "content": "package com.bumptech.glide.gifdecoder;\n\n/*\n * Copyright (c) 2013 Xcellent Creations, Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nimport static com.bumptech.glide.gifdecoder.GifFrame.DISPOSAL_BACKGROUND;\nimport static com.bumptech.glide.gifdecoder.GifFrame.DISPOSAL_NONE;\nimport static com.bumptech.glide.gifdecoder.GifFrame.DISPOSAL_PREVIOUS;\nimport static com.bumptech.glide.gifdecoder.GifFrame.DISPOSAL_UNSPECIFIED;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\nimport android.util.Log;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.util.Arrays;\n\n/**\n * Reads frame data from a GIF image source and decodes it into individual frames for animation\n * purposes.  Image data can be read from either and InputStream source or a byte[].\n *\n * <p>This class is optimized for running animations with the frames, there are no methods to get\n * individual frame images, only to decode the next frame in the animation sequence.  Instead, it\n * lowers its memory footprint by only housing the minimum data necessary to decode the next frame\n * in the animation sequence.\n *\n * <p>The animation must be manually moved forward using {@link #advance()} before requesting the\n * next frame.  This method must also be called before you request the first frame or an error\n * will occur.\n *\n * <p>Implementation adapted from sample code published in Lyons. (2004). <em>Java for\n * Programmers</em>, republished under the MIT Open Source License\n *\n * @see <a href=\"https://www.w3.org/Graphics/GIF/spec-gif89a.txt\">GIF 89a Specification</a>\n */\npublic class StandardGifDecoder implements GifDecoder {\n  private static final String TAG = StandardGifDecoder.class.getSimpleName();\n\n  /** Maximum pixel stack size for decoding LZW compressed data. */\n  private static final int MAX_STACK_SIZE = 4 * 1024;\n\n  private static final int NULL_CODE = -1;\n\n  private static final int INITIAL_FRAME_POINTER = -1;\n\n  private static final int BYTES_PER_INTEGER = Integer.SIZE / 8;\n\n  private static final int MASK_INT_LOWEST_BYTE = 0x000000FF;\n\n  @ColorInt\n  private static final int COLOR_TRANSPARENT_BLACK = 0x00000000;\n\n  // Global File Header values and parsing flags.\n  /**\n   * Active color table.\n   * Maximum size is 256, see GifHeaderParser.readColorTable\n   */\n  @ColorInt\n  private int[] act;\n  /** Private color table that can be modified if needed. */\n  @ColorInt\n  private final int[] pct = new int[256];\n\n  private final GifDecoder.BitmapProvider bitmapProvider;\n\n  /** Raw GIF data from input source. */\n  private ByteBuffer rawData;\n\n  /** Raw data read working array. */\n  private byte[] block;\n\n  private GifHeaderParser parser;\n\n  // LZW decoder working arrays.\n  private short[] prefix;\n  private byte[] suffix;\n  private byte[] pixelStack;\n  private byte[] mainPixels;\n  @ColorInt\n  private int[] mainScratch;\n\n  private int framePointer;\n  private GifHeader header;\n  private Bitmap previousImage;\n  private boolean savePrevious;\n  @GifDecodeStatus\n  private int status;\n  private int sampleSize;\n  private int downsampledHeight;\n  private int downsampledWidth;\n  @Nullable\n  private Boolean isFirstFrameTransparent;\n  @NonNull\n  private Bitmap.Config bitmapConfig = Config.ARGB_8888;\n\n  // Public API.\n  @SuppressWarnings(\"unused\")\n  public StandardGifDecoder(\n      @NonNull GifDecoder.BitmapProvider provider, GifHeader gifHeader, ByteBuffer rawData) {\n    this(provider, gifHeader, rawData, 1 /*sampleSize*/);\n  }\n\n  public StandardGifDecoder(\n      @NonNull GifDecoder.BitmapProvider provider, GifHeader gifHeader, ByteBuffer rawData,\n      int sampleSize) {\n    this(provider);\n    setData(gifHeader, rawData, sampleSize);\n  }\n\n  public StandardGifDecoder(\n      @NonNull GifDecoder.BitmapProvider provider) {\n    this.bitmapProvider = provider;\n    header = new GifHeader();\n  }\n\n  @Override\n  public int getWidth() {\n    return header.width;\n  }\n\n  @Override\n  public int getHeight() {\n    return header.height;\n  }\n\n  @NonNull\n  @Override\n  public ByteBuffer getData() {\n    return rawData;\n  }\n\n  @Override\n  public int getStatus() {\n    return status;\n  }\n\n  @Override\n  public void advance() {\n    framePointer = (framePointer + 1) % header.frameCount;\n  }\n\n  @Override\n  public int getDelay(int n) {\n    int delay = -1;\n    if (n >= 0 && n < header.frameCount) {\n      delay = header.frames.get(n).delay;\n    }\n    return delay;\n  }\n\n  @Override\n  public int getNextDelay() {\n    if (header.frameCount <= 0 || framePointer < 0) {\n      return 0;\n    }\n\n    return getDelay(framePointer);\n  }\n\n  @Override\n  public int getFrameCount() {\n    return header.frameCount;\n  }\n\n  @Override\n  public int getCurrentFrameIndex() {\n    return framePointer;\n  }\n\n  @Override\n  public void resetFrameIndex() {\n    framePointer = INITIAL_FRAME_POINTER;\n  }\n\n  @Deprecated\n  @Override\n  public int getLoopCount() {\n    if (header.loopCount == GifHeader.NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST) {\n      return 1;\n    }\n    return header.loopCount;\n  }\n\n  @Override\n  public int getNetscapeLoopCount() {\n    return header.loopCount;\n  }\n\n  @Override\n  public int getTotalIterationCount() {\n    if (header.loopCount == GifHeader.NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST) {\n      return 1;\n    }\n    if (header.loopCount == GifHeader.NETSCAPE_LOOP_COUNT_FOREVER) {\n      return TOTAL_ITERATION_COUNT_FOREVER;\n    }\n    return header.loopCount + 1;\n  }\n\n  @Override\n  public int getByteSize() {\n    return rawData.limit() + mainPixels.length + (mainScratch.length * BYTES_PER_INTEGER);\n  }\n\n  @Nullable\n  @Override\n  public synchronized Bitmap getNextFrame() {\n    if (header.frameCount <= 0 || framePointer < 0) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Unable to decode frame\"\n            + \", frameCount=\" + header.frameCount\n            + \", framePointer=\" + framePointer\n        );\n      }\n      status = STATUS_FORMAT_ERROR;\n    }\n    if (status == STATUS_FORMAT_ERROR || status == STATUS_OPEN_ERROR) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"Unable to decode frame, status=\" + status);\n      }\n      return null;\n    }\n    status = STATUS_OK;\n\n    if (block == null) {\n      block = bitmapProvider.obtainByteArray(255);\n    }\n\n    GifFrame currentFrame = header.frames.get(framePointer);\n    GifFrame previousFrame = null;\n    int previousIndex = framePointer - 1;\n    if (previousIndex >= 0) {\n      previousFrame = header.frames.get(previousIndex);\n    }\n\n    // Set the appropriate color table.\n    act = currentFrame.lct != null ? currentFrame.lct : header.gct;\n    if (act == null) {\n      if (Log.isLoggable(TAG, Log.DEBUG)) {\n        Log.d(TAG, \"No valid color table found for frame #\" + framePointer);\n      }\n      // No color table defined.\n      status = STATUS_FORMAT_ERROR;\n      return null;\n    }\n\n    // Reset the transparent pixel in the color table\n    if (currentFrame.transparency) {\n      // Prepare local copy of color table (\"pct = act\"), see #1068\n      System.arraycopy(act, 0, pct, 0, act.length);\n      // Forget about act reference from shared header object, use copied version\n      act = pct;\n      // Set transparent color if specified.\n      act[currentFrame.transIndex] = COLOR_TRANSPARENT_BLACK;\n\n      if (currentFrame.dispose == DISPOSAL_BACKGROUND && framePointer == 0) {\n        // TODO: We should check and see if all individual pixels are replaced. If they are, the\n        // first frame isn't actually transparent. For now, it's simpler and safer to assume\n        // drawing a transparent background means the GIF contains transparency.\n        isFirstFrameTransparent = true;\n      }\n    }\n\n    // Transfer pixel data to image.\n    return setPixels(currentFrame, previousFrame);\n  }\n\n  @Override\n  public int read(@Nullable InputStream is, int contentLength) {\n    if (is != null) {\n      try {\n        int capacity = (contentLength > 0) ? (contentLength + 4 * 1024) : 16 * 1024;\n        ByteArrayOutputStream buffer = new ByteArrayOutputStream(capacity);\n        int nRead;\n        byte[] data = new byte[16 * 1024];\n        while ((nRead = is.read(data, 0, data.length)) != -1) {\n          buffer.write(data, 0, nRead);\n        }\n        buffer.flush();\n\n        read(buffer.toByteArray());\n      } catch (IOException e) {\n        Log.w(TAG, \"Error reading data from stream\", e);\n      }\n    } else {\n      status = STATUS_OPEN_ERROR;\n    }\n\n    try {\n      if (is != null) {\n        is.close();\n      }\n    } catch (IOException e) {\n      Log.w(TAG, \"Error closing stream\", e);\n    }\n\n    return status;\n  }\n\n  @Override\n  public void clear() {\n    header = null;\n    if (mainPixels != null) {\n      bitmapProvider.release(mainPixels);\n    }\n    if (mainScratch != null) {\n      bitmapProvider.release(mainScratch);\n    }\n    if (previousImage != null) {\n      bitmapProvider.release(previousImage);\n    }\n    previousImage = null;\n    rawData = null;\n    isFirstFrameTransparent = null;\n    if (block != null) {\n      bitmapProvider.release(block);\n    }\n  }\n\n  @Override\n  public synchronized void setData(@NonNull GifHeader header, @NonNull byte[] data) {\n    setData(header, ByteBuffer.wrap(data));\n  }\n\n  @Override\n  public synchronized void setData(@NonNull GifHeader header, @NonNull ByteBuffer buffer) {\n    setData(header, buffer, 1);\n  }\n\n  @Override\n  public synchronized void setData(@NonNull GifHeader header, @NonNull ByteBuffer buffer,\n      int sampleSize) {\n    if (sampleSize <= 0) {\n      throw new IllegalArgumentException(\"Sample size must be >=0, not: \" + sampleSize);\n    }\n    // Make sure sample size is a power of 2.\n    sampleSize = Integer.highestOneBit(sampleSize);\n    this.status = STATUS_OK;\n    this.header = header;\n    framePointer = INITIAL_FRAME_POINTER;\n    // Initialize the raw data buffer.\n    rawData = buffer.asReadOnlyBuffer();\n    rawData.position(0);\n    rawData.order(ByteOrder.LITTLE_ENDIAN);\n\n    // No point in specially saving an old frame if we're never going to use it.\n    savePrevious = false;\n    for (GifFrame frame : header.frames) {\n      if (frame.dispose == DISPOSAL_PREVIOUS) {\n        savePrevious = true;\n        break;\n      }\n    }\n\n    this.sampleSize = sampleSize;\n    downsampledWidth = header.width / sampleSize;\n    downsampledHeight = header.height / sampleSize;\n    // Now that we know the size, init scratch arrays.\n    // TODO Find a way to avoid this entirely or at least downsample it (either should be possible).\n    mainPixels = bitmapProvider.obtainByteArray(header.width * header.height);\n    mainScratch = bitmapProvider.obtainIntArray(downsampledWidth * downsampledHeight);\n  }\n\n  @NonNull\n  private GifHeaderParser getHeaderParser() {\n    if (parser == null) {\n      parser = new GifHeaderParser();\n    }\n    return parser;\n  }\n\n  @Override\n  @GifDecodeStatus\n  public synchronized int read(@Nullable byte[] data) {\n    this.header = getHeaderParser().setData(data).parseHeader();\n    if (data != null) {\n      setData(header, data);\n    }\n\n    return status;\n  }\n\n  @Override\n  public void setDefaultBitmapConfig(@NonNull Bitmap.Config config) {\n    if (config != Bitmap.Config.ARGB_8888 && config != Bitmap.Config.RGB_565) {\n      throw new IllegalArgumentException(\"Unsupported format: \" + config\n          + \", must be one of \" + Bitmap.Config.ARGB_8888 + \" or \" + Bitmap.Config.RGB_565);\n    }\n\n    bitmapConfig = config;\n  }\n\n  /**\n   * Creates new frame image from current data (and previous frames as specified by their\n   * disposition codes).\n   */\n  private Bitmap setPixels(GifFrame currentFrame, GifFrame previousFrame) {\n    // Final location of blended pixels.\n    final int[] dest = mainScratch;\n\n    // clear all pixels when meet first frame and drop prev image from last loop\n    if (previousFrame == null) {\n      if (previousImage != null) {\n        bitmapProvider.release(previousImage);\n      }\n      previousImage = null;\n      Arrays.fill(dest, COLOR_TRANSPARENT_BLACK);\n    }\n\n    // clear all pixels when dispose is 3 but previousImage is null.\n    // When DISPOSAL_PREVIOUS and previousImage didn't be set, new frame should draw on\n    // a empty image\n    if (previousFrame != null && previousFrame.dispose == DISPOSAL_PREVIOUS\n            && previousImage == null) {\n      Arrays.fill(dest, COLOR_TRANSPARENT_BLACK);\n    }\n\n    // fill in starting image contents based on last image's dispose code\n    if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) {\n      // We don't need to do anything for DISPOSAL_NONE, if it has the correct pixels so will our\n      // mainScratch and therefore so will our dest array.\n      if (previousFrame.dispose == DISPOSAL_BACKGROUND) {\n        // Start with a canvas filled with the background color\n        @ColorInt int c = COLOR_TRANSPARENT_BLACK;\n        if (!currentFrame.transparency) {\n          c = header.bgColor;\n          if (currentFrame.lct != null && header.bgIndex == currentFrame.transIndex) {\n            c = COLOR_TRANSPARENT_BLACK;\n          }\n        }\n        // The area used by the graphic must be restored to the background color.\n        int downsampledIH = previousFrame.ih / sampleSize;\n        int downsampledIY = previousFrame.iy / sampleSize;\n        int downsampledIW = previousFrame.iw / sampleSize;\n        int downsampledIX = previousFrame.ix / sampleSize;\n        int topLeft = downsampledIY * downsampledWidth + downsampledIX;\n        int bottomLeft = topLeft + downsampledIH * downsampledWidth;\n        for (int left = topLeft; left < bottomLeft; left += downsampledWidth) {\n          int right = left + downsampledIW;\n          for (int pointer = left; pointer < right; pointer++) {\n            dest[pointer] = c;\n          }\n        }\n      } else if (previousFrame.dispose == DISPOSAL_PREVIOUS && previousImage != null) {\n        // Start with the previous frame\n        previousImage.getPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth,\n            downsampledHeight);\n      }\n    }\n\n    // Decode pixels for this frame into the global pixels[] scratch.\n    decodeBitmapData(currentFrame);\n\n    if (currentFrame.interlace || sampleSize != 1) {\n      copyCopyIntoScratchRobust(currentFrame);\n    } else {\n      copyIntoScratchFast(currentFrame);\n    }\n\n    // Copy pixels into previous image\n    if (savePrevious && (currentFrame.dispose == DISPOSAL_UNSPECIFIED\n        || currentFrame.dispose == DISPOSAL_NONE)) {\n      if (previousImage == null) {\n        previousImage = getNextBitmap();\n      }\n      previousImage.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth,\n          downsampledHeight);\n    }\n\n    // Set pixels for current image.\n    Bitmap result = getNextBitmap();\n    result.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight);\n    return result;\n  }\n\n  @SuppressWarnings(\"checkstyle:UnnecessaryParentheses\") // Readability\n  private void copyIntoScratchFast(GifFrame currentFrame) {\n    int[] dest = mainScratch;\n    int downsampledIH = currentFrame.ih;\n    int downsampledIY = currentFrame.iy;\n    int downsampledIW = currentFrame.iw;\n    int downsampledIX = currentFrame.ix;\n    // Copy each source line to the appropriate place in the destination.\n    boolean isFirstFrame = framePointer == 0;\n    int width = this.downsampledWidth;\n    byte[] mainPixels = this.mainPixels;\n    int[] act = this.act;\n    byte transparentColorIndex = -1;\n    for (int i = 0; i < downsampledIH; i++) {\n      int line = i + downsampledIY;\n      int k = line * width;\n      // Start of line in dest.\n      int dx = k + downsampledIX;\n      // End of dest line.\n      int dlim = dx + downsampledIW;\n      if (k + width < dlim) {\n        // Past dest edge.\n        dlim = k + width;\n      }\n      // Start of line in source.\n      int sx = i * currentFrame.iw;\n\n      while (dx < dlim) {\n        byte byteCurrentColorIndex = mainPixels[sx];\n        int currentColorIndex = ((int) byteCurrentColorIndex) & MASK_INT_LOWEST_BYTE;\n        if (currentColorIndex != transparentColorIndex) {\n          int color = act[currentColorIndex];\n          if (color != COLOR_TRANSPARENT_BLACK) {\n            dest[dx] = color;\n          } else {\n            transparentColorIndex = byteCurrentColorIndex;\n          }\n        }\n        ++sx;\n        ++dx;\n      }\n    }\n\n    isFirstFrameTransparent =\n        (isFirstFrameTransparent != null && isFirstFrameTransparent)\n            || (isFirstFrameTransparent == null && isFirstFrame && transparentColorIndex != -1);\n  }\n\n  private void copyCopyIntoScratchRobust(GifFrame currentFrame) {\n    int[] dest = mainScratch;\n    int downsampledIH = currentFrame.ih / sampleSize;\n    int downsampledIY = currentFrame.iy / sampleSize;\n    int downsampledIW = currentFrame.iw / sampleSize;\n    int downsampledIX = currentFrame.ix / sampleSize;\n    // Copy each source line to the appropriate place in the destination.\n    int pass = 1;\n    int inc = 8;\n    int iline = 0;\n    boolean isFirstFrame = framePointer == 0;\n    int sampleSize = this.sampleSize;\n    int downsampledWidth = this.downsampledWidth;\n    int downsampledHeight = this.downsampledHeight;\n    byte[] mainPixels = this.mainPixels;\n    int[] act = this.act;\n    @Nullable\n    Boolean isFirstFrameTransparent = this.isFirstFrameTransparent;\n    for (int i = 0; i < downsampledIH; i++) {\n      int line = i;\n      if (currentFrame.interlace) {\n        if (iline >= downsampledIH) {\n          pass++;\n          switch (pass) {\n            case 2:\n              iline = 4;\n              break;\n            case 3:\n              iline = 2;\n              inc = 4;\n              break;\n            case 4:\n              iline = 1;\n              inc = 2;\n              break;\n            default:\n              break;\n          }\n        }\n        line = iline;\n        iline += inc;\n      }\n      line += downsampledIY;\n      boolean isNotDownsampling = sampleSize == 1;\n      if (line < downsampledHeight) {\n        int k = line * downsampledWidth;\n        // Start of line in dest.\n        int dx = k + downsampledIX;\n        // End of dest line.\n        int dlim = dx + downsampledIW;\n        if (k + downsampledWidth < dlim) {\n          // Past dest edge.\n          dlim = k + downsampledWidth;\n        }\n        // Start of line in source.\n        int sx = i * sampleSize * currentFrame.iw;\n        if (isNotDownsampling) {\n          int averageColor;\n          while (dx < dlim) {\n            int currentColorIndex = ((int) mainPixels[sx]) & MASK_INT_LOWEST_BYTE;\n            averageColor = act[currentColorIndex];\n            if (averageColor != COLOR_TRANSPARENT_BLACK) {\n              dest[dx] = averageColor;\n            } else if (isFirstFrame && isFirstFrameTransparent == null) {\n              isFirstFrameTransparent = true;\n            }\n            sx += sampleSize;\n            dx++;\n          }\n        } else {\n          int averageColor;\n          int maxPositionInSource = sx + ((dlim - dx) * sampleSize);\n          while (dx < dlim) {\n            // Map color and insert in destination.\n            // TODO: This is substantially slower (up to 50ms per frame) than just grabbing the\n            // current color index above, even with a sample size of 1.\n            averageColor = averageColorsNear(sx, maxPositionInSource, currentFrame.iw);\n            if (averageColor != COLOR_TRANSPARENT_BLACK) {\n              dest[dx] = averageColor;\n            } else if (isFirstFrame && isFirstFrameTransparent == null) {\n              isFirstFrameTransparent = true;\n            }\n            sx += sampleSize;\n            dx++;\n          }\n        }\n      }\n    }\n\n    if (this.isFirstFrameTransparent == null) {\n      this.isFirstFrameTransparent = isFirstFrameTransparent == null\n          ? false : isFirstFrameTransparent;\n    }\n  }\n\n  @ColorInt\n  private int averageColorsNear(int positionInMainPixels, int maxPositionInMainPixels,\n      int currentFrameIw) {\n    int alphaSum = 0;\n    int redSum = 0;\n    int greenSum = 0;\n    int blueSum = 0;\n\n    int totalAdded = 0;\n    // Find the pixels in the current row.\n    for (int i = positionInMainPixels;\n         i < positionInMainPixels + sampleSize && i < mainPixels.length\n             && i < maxPositionInMainPixels; i++) {\n      int currentColorIndex = ((int) mainPixels[i]) & MASK_INT_LOWEST_BYTE;\n      int currentColor = act[currentColorIndex];\n      if (currentColor != 0) {\n        alphaSum += currentColor >> 24 & MASK_INT_LOWEST_BYTE;\n        redSum += currentColor >> 16 & MASK_INT_LOWEST_BYTE;\n        greenSum += currentColor >> 8 & MASK_INT_LOWEST_BYTE;\n        blueSum += currentColor & MASK_INT_LOWEST_BYTE;\n        totalAdded++;\n      }\n    }\n    // Find the pixels in the next row.\n    for (int i = positionInMainPixels + currentFrameIw;\n         i < positionInMainPixels + currentFrameIw + sampleSize && i < mainPixels.length\n             && i < maxPositionInMainPixels; i++) {\n      int currentColorIndex = ((int) mainPixels[i]) & MASK_INT_LOWEST_BYTE;\n      int currentColor = act[currentColorIndex];\n      if (currentColor != 0) {\n        alphaSum += currentColor >> 24 & MASK_INT_LOWEST_BYTE;\n        redSum += currentColor >> 16 & MASK_INT_LOWEST_BYTE;\n        greenSum += currentColor >> 8 & MASK_INT_LOWEST_BYTE;\n        blueSum += currentColor & MASK_INT_LOWEST_BYTE;\n        totalAdded++;\n      }\n    }\n    if (totalAdded == 0) {\n      return COLOR_TRANSPARENT_BLACK;\n    } else {\n      return ((alphaSum / totalAdded) << 24)\n          | ((redSum / totalAdded) << 16)\n          | ((greenSum / totalAdded) << 8)\n          | (blueSum / totalAdded);\n    }\n  }\n\n  /**\n   * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.\n   */\n  private void decodeBitmapData(GifFrame frame) {\n    if (frame != null) {\n      // Jump to the frame start position.\n      rawData.position(frame.bufferFrameStart);\n    }\n\n    int npix = (frame == null) ? header.width * header.height : frame.iw * frame.ih;\n    int available, clear, codeMask, codeSize, endOfInformation, inCode, oldCode, bits, code, count,\n        i, datum, dataSize, first, top, bi, pi;\n\n    if (mainPixels == null || mainPixels.length < npix) {\n      // Allocate new pixel array.\n      mainPixels = bitmapProvider.obtainByteArray(npix);\n    }\n    byte[] mainPixels = this.mainPixels;\n    if (prefix == null) {\n      prefix = new short[MAX_STACK_SIZE];\n    }\n    short[] prefix = this.prefix;\n    if (suffix == null) {\n      suffix = new byte[MAX_STACK_SIZE];\n    }\n    byte[] suffix = this.suffix;\n    if (pixelStack == null) {\n      pixelStack = new byte[MAX_STACK_SIZE + 1];\n    }\n    byte[] pixelStack = this.pixelStack;\n\n    // Initialize GIF data stream decoder.\n    dataSize = readByte();\n    clear = 1 << dataSize;\n    endOfInformation = clear + 1;\n    available = clear + 2;\n    oldCode = NULL_CODE;\n    codeSize = dataSize + 1;\n    codeMask = (1 << codeSize) - 1;\n\n    for (code = 0; code < clear; code++) {\n      // XXX ArrayIndexOutOfBoundsException.\n      prefix[code] = 0;\n      suffix[code] = (byte) code;\n    }\n    byte[] block = this.block;\n    // Decode GIF pixel stream.\n    i = datum = bits = count = first = top = pi = bi = 0;\n    while (i < npix) {\n      // Read a new data block.\n      if (count == 0) {\n        count = readBlock();\n        if (count <= 0) {\n          status = STATUS_PARTIAL_DECODE;\n          break;\n        }\n        bi = 0;\n      }\n\n      datum += (((int) block[bi]) & MASK_INT_LOWEST_BYTE) << bits;\n      bits += 8;\n      ++bi;\n      --count;\n\n      while (bits >= codeSize) {\n        // Get the next code.\n        code = datum & codeMask;\n        datum >>= codeSize;\n        bits -= codeSize;\n\n        // Interpret the code.\n        if (code == clear) {\n          // Reset decoder.\n          codeSize = dataSize + 1;\n          codeMask = (1 << codeSize) - 1;\n          available = clear + 2;\n          oldCode = NULL_CODE;\n          continue;\n        } else if (code == endOfInformation) {\n          break;\n        } else if (oldCode == NULL_CODE) {\n          mainPixels[pi] = suffix[code];\n          ++pi;\n          ++i;\n          oldCode = code;\n          first = code;\n          continue;\n        }\n\n        inCode = code;\n        if (code >= available) {\n          pixelStack[top] = (byte) first;\n          ++top;\n          code = oldCode;\n        }\n\n        while (code >= clear) {\n          pixelStack[top] = suffix[code];\n          ++top;\n          code = prefix[code];\n        }\n        first = ((int) suffix[code]) & MASK_INT_LOWEST_BYTE;\n\n        mainPixels[pi] = (byte) first;\n        ++pi;\n        ++i;\n\n        while (top > 0) {\n          // Pop a pixel off the pixel stack.\n          mainPixels[pi] = pixelStack[--top];\n          ++pi;\n          ++i;\n        }\n\n        // Add a new string to the string table.\n        if (available < MAX_STACK_SIZE) {\n          prefix[available] = (short) oldCode;\n          suffix[available] = (byte) first;\n          ++available;\n          if ((available & codeMask) == 0 && available < MAX_STACK_SIZE) {\n            ++codeSize;\n            codeMask += available;\n          }\n        }\n        oldCode = inCode;\n      }\n    }\n\n    // Clear missing pixels.\n    Arrays.fill(mainPixels, pi, npix, (byte) COLOR_TRANSPARENT_BLACK);\n  }\n\n  /**\n   * Reads a single byte from the input stream.\n   */\n  private int readByte() {\n    return rawData.get() & MASK_INT_LOWEST_BYTE;\n  }\n\n  /**\n   * Reads next variable length block from input.\n   *\n   * @return number of bytes stored in \"buffer\".\n   */\n  private int readBlock() {\n    int blockSize = readByte();\n    if (blockSize <= 0) {\n      return blockSize;\n    }\n    rawData.get(block, 0, Math.min(blockSize, rawData.remaining()));\n    return blockSize;\n  }\n\n  private Bitmap getNextBitmap() {\n    Bitmap.Config config = isFirstFrameTransparent == null || isFirstFrameTransparent\n        ? Bitmap.Config.ARGB_8888 : bitmapConfig;\n    Bitmap result = bitmapProvider.obtain(downsampledWidth, downsampledHeight, config);\n    result.setHasAlpha(true);\n    return result;\n  }\n}\n"
  },
  {
    "path": "third_party/gif_decoder/src/test/java/com/bumptech/glide/gifdecoder/GifDecoderTest.java",
    "content": "package com.bumptech.glide.gifdecoder;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\nimport android.graphics.Bitmap;\nimport androidx.annotation.NonNull;\nimport com.bumptech.glide.testutil.TestUtil;\nimport java.io.IOException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.Shadows;\nimport org.robolectric.annotation.Config;\n\n/** Tests for {@link com.bumptech.glide.gifdecoder.GifDecoder}. */\n@RunWith(RobolectricTestRunner.class)\n@Config(sdk = 19)\npublic class GifDecoderTest {\n\n  private MockProvider provider;\n\n  @Before\n  public void setUp() {\n    provider = new MockProvider();\n  }\n\n  @Test\n  public void testCorrectPixelsDecoded() throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"white_black_row.gif\");\n    GifHeaderParser headerParser = new GifHeaderParser();\n    headerParser.setData(data);\n    GifHeader header = headerParser.parseHeader();\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(header, data);\n    decoder.advance();\n    Bitmap bitmap = decoder.getNextFrame();\n    assertNotNull(bitmap);\n    assertEquals(bitmap.getPixel(2, 0), bitmap.getPixel(0, 0));\n    assertEquals(bitmap.getPixel(3, 0), bitmap.getPixel(1, 0));\n  }\n\n  @Test\n  public void testCanDecodeFramesFromTestGif() throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"partial_gif_decode.gif\");\n    GifHeaderParser headerParser = new GifHeaderParser();\n    headerParser.setData(data);\n    GifHeader header = headerParser.parseHeader();\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(header, data);\n    decoder.advance();\n    Bitmap bitmap = decoder.getNextFrame();\n    assertNotNull(bitmap);\n    assertEquals(GifDecoder.STATUS_OK, decoder.getStatus());\n  }\n\n  @Test\n  public void testFrameIndexStartsAtNegativeOne() {\n    GifHeader gifheader = new GifHeader();\n    gifheader.frameCount = 4;\n    byte[] data = new byte[0];\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(gifheader, data);\n    assertEquals(-1, decoder.getCurrentFrameIndex());\n  }\n\n  @Test\n  public void testTotalIterationCountIsOneIfNetscapeLoopCountDoesntExist() {\n    GifHeader gifheader = new GifHeader();\n    gifheader.loopCount = GifHeader.NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST;\n    byte[] data = new byte[0];\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(gifheader, data);\n    assertEquals(1, decoder.getTotalIterationCount());\n  }\n\n  @Test\n  public void testTotalIterationCountIsForeverIfNetscapeLoopCountIsForever() {\n    GifHeader gifheader = new GifHeader();\n    gifheader.loopCount = GifHeader.NETSCAPE_LOOP_COUNT_FOREVER;\n    byte[] data = new byte[0];\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(gifheader, data);\n    assertEquals(GifDecoder.TOTAL_ITERATION_COUNT_FOREVER, decoder.getTotalIterationCount());\n  }\n\n  @Test\n  public void testTotalIterationCountIsTwoIfNetscapeLoopCountIsOne() {\n    GifHeader gifheader = new GifHeader();\n    gifheader.loopCount = 1;\n    byte[] data = new byte[0];\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(gifheader, data);\n    assertEquals(2, decoder.getTotalIterationCount());\n  }\n\n  @Test\n  public void testAdvanceIncrementsFrameIndex() {\n    GifHeader gifheader = new GifHeader();\n    gifheader.frameCount = 4;\n    byte[] data = new byte[0];\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(gifheader, data);\n    decoder.advance();\n    assertEquals(0, decoder.getCurrentFrameIndex());\n  }\n\n  @Test\n  public void testAdvanceWrapsIndexBackToZero() {\n    GifHeader gifheader = new GifHeader();\n    gifheader.frameCount = 2;\n    byte[] data = new byte[0];\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(gifheader, data);\n    decoder.advance();\n    decoder.advance();\n    decoder.advance();\n    assertEquals(0, decoder.getCurrentFrameIndex());\n  }\n\n  @Test\n  public void testSettingDataResetsFramePointer() {\n    GifHeader gifheader = new GifHeader();\n    gifheader.frameCount = 4;\n    byte[] data = new byte[0];\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(gifheader, data);\n    decoder.advance();\n    decoder.advance();\n    assertEquals(1, decoder.getCurrentFrameIndex());\n\n    decoder.setData(gifheader, data);\n    assertEquals(-1, decoder.getCurrentFrameIndex());\n  }\n\n  @Test\n  public void testFirstFrameMustClearBeforeDrawingWhenLastFrameIsDisposalBackground()\n      throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"transparent_disposal_background.gif\");\n    GifHeaderParser headerParser = new GifHeaderParser();\n    headerParser.setData(data);\n    GifHeader header = headerParser.parseHeader();\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(header, data);\n    decoder.advance();\n    Bitmap firstFrame = decoder.getNextFrame();\n    decoder.advance();\n    decoder.getNextFrame();\n    decoder.advance();\n    Bitmap firstFrameTwice = decoder.getNextFrame();\n    assertTrue(firstFrame.sameAs(firstFrameTwice));\n  }\n\n  @Test\n  public void testFirstFrameMustClearBeforeDrawingWhenLastFrameIsDisposalNone() throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"transparent_disposal_none.gif\");\n    GifHeaderParser headerParser = new GifHeaderParser();\n    headerParser.setData(data);\n    GifHeader header = headerParser.parseHeader();\n    GifDecoder decoder = new StandardGifDecoder(provider);\n    decoder.setData(header, data);\n    decoder.advance();\n    Bitmap firstFrame = decoder.getNextFrame();\n    decoder.advance();\n    decoder.getNextFrame();\n    decoder.advance();\n    Bitmap firstFrameTwice = decoder.getNextFrame();\n    assertTrue(firstFrame.sameAs(firstFrameTwice));\n  }\n\n  private static class MockProvider implements GifDecoder.BitmapProvider {\n\n    @NonNull\n    @Override\n    public Bitmap obtain(int width, int height, Bitmap.Config config) {\n      Bitmap result = Bitmap.createBitmap(width, height, config);\n      Shadows.shadowOf(result).setMutable(true);\n      return result;\n    }\n\n    @Override\n    public void release(@NonNull Bitmap bitmap) {\n      // Do nothing.\n    }\n\n    @NonNull\n    @Override\n    public byte[] obtainByteArray(int size) {\n      return new byte[size];\n    }\n\n    @Override\n    public void release(@NonNull byte[] bytes) {\n      // Do nothing.\n    }\n\n    @NonNull\n    @Override\n    public int[] obtainIntArray(int size) {\n      return new int[size];\n    }\n\n    @Override\n    public void release(@NonNull int[] array) {\n      // Do Nothing\n    }\n  }\n}\n"
  },
  {
    "path": "third_party/gif_decoder/src/test/java/com/bumptech/glide/gifdecoder/GifHeaderParserTest.java",
    "content": "package com.bumptech.glide.gifdecoder;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\nimport com.bumptech.glide.gifdecoder.test.GifBytesTestUtil;\nimport com.bumptech.glide.testutil.TestUtil;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Tests for {@link com.bumptech.glide.gifdecoder.GifHeaderParser}.\n */\n@RunWith(JUnit4.class)\npublic class GifHeaderParserTest {\n  private GifHeaderParser parser;\n\n  @Before\n  public void setUp() {\n    parser = new GifHeaderParser();\n  }\n\n  @Test\n  public void testReturnsHeaderWithFormatErrorIfDoesNotStartWithGifHeader() {\n    parser.setData(\"wrong_header\".getBytes());\n    GifHeader result = parser.parseHeader();\n    assertEquals(GifDecoder.STATUS_FORMAT_ERROR, result.status);\n  }\n\n  @Test\n  public void testCanReadValidHeaderAndLSD() {\n    final int width = 10;\n    final int height = 20;\n    ByteBuffer buffer =\n        ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, width, height, false, 0);\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    assertEquals(width, header.width);\n    assertEquals(height, header.height);\n    assertFalse(header.gctFlag);\n    // 2^(1+0) == 2^1 == 2.\n    assertEquals(2, header.gctSize);\n    assertEquals(0, header.bgIndex);\n    assertEquals(0, header.pixelAspect);\n  }\n\n  @Test\n  public void testCanParseHeaderOfTestImageWithoutGraphicalExtension() throws IOException {\n    byte[] data =\n        TestUtil.resourceToBytes(getClass(), \"gif_without_graphical_control_extension.gif\");\n    parser.setData(data);\n    GifHeader header = parser.parseHeader();\n    assertEquals(1, header.frameCount);\n    assertNotNull(header.frames.get(0));\n    assertEquals(GifDecoder.STATUS_OK, header.status);\n  }\n\n  @Test\n  public void testCanReadNetscapeIterationCountIfNetscapeIterationCountIsZero() throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"gif_netscape_iteration_0.gif\");\n    parser.setData(data);\n    GifHeader header = parser.parseHeader();\n    assertEquals(GifHeader.NETSCAPE_LOOP_COUNT_FOREVER, header.loopCount);\n  }\n\n  @Test\n  public void testCanReadNetscapeIterationCountIfNetscapeIterationCountIs_1() throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"gif_netscape_iteration_1.gif\");\n    parser.setData(data);\n    GifHeader header = parser.parseHeader();\n    assertEquals(1, header.loopCount);\n  }\n\n  @Test\n  public void testCanReadNetscapeIterationCountIfNetscapeIterationCountIs_0x0F()\n      throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"gif_netscape_iteration_255.gif\");\n    parser.setData(data);\n    GifHeader header = parser.parseHeader();\n    assertEquals(255, header.loopCount);\n  }\n\n  @Test\n  public void testCanReadNetscapeIterationCountIfNetscapeIterationCountIs_0x10()\n      throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"gif_netscape_iteration_256.gif\");\n    parser.setData(data);\n    GifHeader header = parser.parseHeader();\n    assertEquals(256, header.loopCount);\n  }\n\n  @Test\n  public void testCanReadNetscapeIterationCountIfNetscapeIterationCountIs_0xFF()\n      throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"gif_netscape_iteration_65535.gif\");\n    parser.setData(data);\n    GifHeader header = parser.parseHeader();\n    assertEquals(65535, header.loopCount);\n  }\n\n  @Test\n  public void testLoopCountReturnsMinusOneWithoutNetscapeIterationCount()\n          throws IOException {\n    byte[] data = TestUtil.resourceToBytes(getClass(), \"gif_without_netscape_iteration.gif\");\n    parser.setData(data);\n    GifHeader header = parser.parseHeader();\n    assertEquals(GifHeader.NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST, header.loopCount);\n  }\n\n  @Test\n  public void testCanReadImageDescriptorWithoutGraphicalExtension() {\n    final int lzwMinCodeSize = 2;\n    ByteBuffer buffer = ByteBuffer.allocate(\n        GifBytesTestUtil.HEADER_LENGTH + GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH + GifBytesTestUtil\n            .getImageDataSize()).order(ByteOrder.LITTLE_ENDIAN);\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 1, 1, false, 0);\n    GifBytesTestUtil.writeImageDescriptor(buffer, 0, 0, 1, 1, false /*hasLct*/, 0);\n    GifBytesTestUtil.writeFakeImageData(buffer, lzwMinCodeSize);\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    assertEquals(1, header.width);\n    assertEquals(1, header.height);\n    assertEquals(1, header.frameCount);\n    assertNotNull(header.frames.get(0));\n  }\n\n  private static ByteBuffer writeHeaderWithGceAndFrameDelay(short frameDelay) {\n    final int lzwMinCodeSize = 2;\n    ByteBuffer buffer = ByteBuffer.allocate(\n        GifBytesTestUtil.HEADER_LENGTH + GifBytesTestUtil.GRAPHICS_CONTROL_EXTENSION_LENGTH\n            + GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH + GifBytesTestUtil\n            .getImageDataSize()).order(ByteOrder.LITTLE_ENDIAN);\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 1, 1, false, 0);\n    GifBytesTestUtil.writeGraphicsControlExtension(buffer, frameDelay);\n    GifBytesTestUtil.writeImageDescriptor(buffer, 0, 0, 1, 1, false /*hasLct*/, 0);\n    GifBytesTestUtil.writeFakeImageData(buffer, lzwMinCodeSize);\n    return buffer;\n  }\n\n  @Test\n  public void testCanParseFrameDelay() {\n    final short frameDelay = 50;\n    ByteBuffer buffer = writeHeaderWithGceAndFrameDelay(frameDelay);\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    GifFrame frame = header.frames.get(0);\n\n    // Convert delay in 100ths of a second to ms.\n    assertEquals(frameDelay * 10, frame.delay);\n  }\n\n  @Test\n  public void testSetsDefaultFrameDelayIfFrameDelayIsZero() {\n    ByteBuffer buffer = writeHeaderWithGceAndFrameDelay((short) 0);\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    GifFrame frame = header.frames.get(0);\n\n    // Convert delay in 100ths of a second to ms.\n    assertEquals(GifHeaderParser.DEFAULT_FRAME_DELAY * 10, frame.delay);\n  }\n\n  @Test\n  public void testSetsDefaultFrameDelayIfFrameDelayIsLessThanMinimum() {\n    final short frameDelay = GifHeaderParser.MIN_FRAME_DELAY - 1;\n    ByteBuffer buffer = writeHeaderWithGceAndFrameDelay(frameDelay);\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    GifFrame frame = header.frames.get(0);\n\n    // Convert delay in 100ths of a second to ms.\n    assertEquals(GifHeaderParser.DEFAULT_FRAME_DELAY * 10, frame.delay);\n  }\n\n  @Test\n  public void testObeysFrameDelayIfFrameDelayIsAtMinimum() {\n    final short frameDelay = GifHeaderParser.MIN_FRAME_DELAY;\n    ByteBuffer buffer = writeHeaderWithGceAndFrameDelay(frameDelay);\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    GifFrame frame = header.frames.get(0);\n\n    // Convert delay in 100ths of a second to ms.\n    assertEquals(frameDelay * 10, frame.delay);\n  }\n\n  @Test\n  public void testSetsFrameLocalColorTableToNullIfNoColorTable() {\n    final int lzwMinCodeSize = 2;\n    ByteBuffer buffer = ByteBuffer.allocate(\n        GifBytesTestUtil.HEADER_LENGTH + GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH + GifBytesTestUtil\n            .getImageDataSize()).order(ByteOrder.LITTLE_ENDIAN);\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 1, 1, false, 0);\n    GifBytesTestUtil.writeImageDescriptor(buffer, 0, 0, 1, 1, false /*hasLct*/, 0);\n    GifBytesTestUtil.writeFakeImageData(buffer, lzwMinCodeSize);\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    assertEquals(1, header.width);\n    assertEquals(1, header.height);\n    assertEquals(1, header.frameCount);\n    assertNotNull(header.frames.get(0));\n    assertNull(header.frames.get(0).lct);\n  }\n\n  @Test\n  public void testSetsFrameLocalColorTableIfHasColorTable() {\n    final int lzwMinCodeSize = 2;\n    final int numColors = 4;\n    ByteBuffer buffer = ByteBuffer.allocate(\n        GifBytesTestUtil.HEADER_LENGTH + GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH + GifBytesTestUtil\n            .getImageDataSize() + GifBytesTestUtil.getColorTableLength(numColors))\n        .order(ByteOrder.LITTLE_ENDIAN);\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 1, 1, false, 0);\n    GifBytesTestUtil.writeImageDescriptor(buffer, 0, 0, 1, 1, true /*hasLct*/, numColors);\n    GifBytesTestUtil.writeColorTable(buffer, numColors);\n    GifBytesTestUtil.writeFakeImageData(buffer, 2);\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    assertEquals(1, header.width);\n    assertEquals(1, header.height);\n    assertEquals(1, header.frameCount);\n    assertNotNull(header.frames.get(0));\n\n    GifFrame frame = header.frames.get(0);\n    assertNotNull(frame.lct);\n  }\n\n  @Test\n  public void testCanParseMultipleFrames() {\n    final int lzwMinCodeSize = 2;\n    final int expectedFrames = 3;\n\n    final int frameSize = GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH + GifBytesTestUtil\n        .getImageDataSize();\n    ByteBuffer buffer =\n        ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH + expectedFrames * frameSize)\n            .order(ByteOrder.LITTLE_ENDIAN);\n\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 1, 1, false, 0);\n    for (int i = 0; i < expectedFrames; i++) {\n      GifBytesTestUtil.writeImageDescriptor(buffer, 0, 0, 1, 1, false /*hasLct*/, 0 /*numColors*/);\n      GifBytesTestUtil.writeFakeImageData(buffer, 2);\n    }\n\n    parser.setData(buffer.array());\n    GifHeader header = parser.parseHeader();\n    assertEquals(expectedFrames, header.frameCount);\n    assertEquals(expectedFrames, header.frames.size());\n  }\n\n  @Test\n  public void testIsAnimatedMultipleFrames() {\n    final int lzwMinCodeSize = 2;\n    final int numFrames = 3;\n\n    final int frameSize =\n        GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH\n            + GifBytesTestUtil.getImageDataSize();\n    ByteBuffer buffer =\n        ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH + numFrames * frameSize)\n            .order(ByteOrder.LITTLE_ENDIAN);\n\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 1, 1, false, 0);\n    for (int i = 0; i < numFrames; i++) {\n      GifBytesTestUtil.writeImageDescriptor(buffer, 0, 0, 1, 1, false /*hasLct*/, 0 /*numColors*/);\n      GifBytesTestUtil.writeFakeImageData(buffer, 2);\n    }\n\n    parser.setData(buffer.array());\n    assertTrue(parser.isAnimated());\n  }\n\n  @Test\n  public void testIsNotAnimatedOneFrame() {\n    final int lzwMinCodeSize = 2;\n\n    final int frameSize =\n        GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH\n            + GifBytesTestUtil.getImageDataSize();\n\n    ByteBuffer buffer =\n        ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH + frameSize)\n            .order(ByteOrder.LITTLE_ENDIAN);\n\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 1, 1, false, 0);\n    GifBytesTestUtil.writeImageDescriptor(buffer, 0, 0, 1, 1, false /*hasLct*/, 0 /*numColors*/);\n    GifBytesTestUtil.writeFakeImageData(buffer, 2);\n\n    parser.setData(buffer.array());\n    assertFalse(parser.isAnimated());\n  }\n\n\n  @Test(expected = IllegalStateException.class)\n  public void testThrowsIfParseHeaderCalledBeforeSetData() {\n    GifHeaderParser parser = new GifHeaderParser();\n    parser.parseHeader();\n  }\n}\n"
  },
  {
    "path": "third_party/gif_decoder/src/test/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtil.java",
    "content": "package com.bumptech.glide.gifdecoder.test;\n\nimport java.nio.ByteBuffer;\n\n/**\n * Utils for writing the bytes of various parts of GIFs to byte buffers.\n */\npublic class GifBytesTestUtil {\n  // Length in bytes.\n  public static final int HEADER_LENGTH = 13;\n  // Length in bytes.\n  public static final int IMAGE_DESCRIPTOR_LENGTH = 10;\n  // Length in bytes.\n  public static final int GRAPHICS_CONTROL_EXTENSION_LENGTH = 8;\n\n  public static int getColorTableLength(int numColors) {\n    return 3 * numColors;\n  }\n\n  public static int getImageDataSize() {\n    // TODO: fill this out.\n    return 4;\n  }\n\n  public static void writeFakeImageData(ByteBuffer out, int lzwMinCodeSize) {\n    // 1 for lzwMinCodeSize, 1 for length, 1 for min content, 1 for block terminator.\n    verifyRemaining(out, 4);\n    verifyShortValues(lzwMinCodeSize);\n\n    out.put((byte) lzwMinCodeSize);\n    // Block length.\n    out.put((byte) 0x01);\n    // Block content.\n    out.put((byte) 0x01);\n    // End of block.\n    out.put((byte) 0x00);\n  }\n\n  public static void writeColorTable(ByteBuffer out, int numColors) {\n    verifyRemaining(out, getColorTableLength(numColors));\n    for (int i = 0; i < numColors; i++) {\n      out.put((byte) (0xFF0000 & i));\n      out.put((byte) (0x00FF00 & i));\n      out.put((byte) (0x0000FF & i));\n    }\n  }\n\n  public static void writeImageDescriptor(ByteBuffer out, int imageLeft, int imageTop,\n      int imageWidth, int imageHeight, boolean hasLct, int numColors) {\n    verifyRemaining(out, IMAGE_DESCRIPTOR_LENGTH);\n    verifyShortValues(imageLeft, imageTop, imageWidth, imageHeight);\n\n    final byte packed;\n    if (hasLct) {\n      int size = log2(numColors) - 1;\n      packed = (byte) (0x80 | size);\n    } else {\n      packed = 0x00;\n    }\n\n    // Image separator\n    out.put((byte) 0x2C);\n    out.putShort((short) imageLeft).putShort((short) imageTop).putShort((short) imageWidth)\n        .putShort((short) imageHeight).put(packed);\n  }\n\n  private static int log2(int num) {\n    return (int) Math.round(Math.log(num) / Math.log(2));\n  }\n\n  public static void writeHeaderAndLsd(ByteBuffer out, int width, int height, boolean hasGct,\n      int gctSize) {\n    verifyRemaining(out, HEADER_LENGTH);\n    verifyShortValues(width, height);\n\n    // GIF\n    out.put((byte) 0x47).put((byte) 0x49).put((byte) 0x46);\n    // Version - 89a.\n    out.put((byte) 0x38).put((byte) 0x39).put((byte) 0x61);\n\n    /* LSD (Logical Screen Descriptor) **/\n    // Width.\n    out.putShort((short) width);\n    // Height.\n    out.putShort((short) height);\n    // Packed GCT (Global Color Table) flag + color resolution + sort flag + size of GCT.\n    // GCT flag (false) - most significant bit.\n    byte gctFlag = (byte) ((hasGct ? 1 : 0) << 7);\n    // Color resolution - next three bits.\n    byte colorResolution = 1 << 5;\n    // Sort flag - next bit;\n    byte sortFlag = 0;\n    // exponent of size of color table, size = 2^(1 + exponent) - least significant 3 bits.\n    byte size = (byte) gctSize;\n\n    byte packed = (byte) (gctFlag | colorResolution | sortFlag | size);\n    out.put(packed);\n\n    // Background color index.\n    out.put((byte) 0);\n\n    // Pixel aspect ratio.\n    out.put((byte) 0);\n  }\n\n  public static void writeGraphicsControlExtension(ByteBuffer out, int delayTime) {\n    verifyRemaining(out, GRAPHICS_CONTROL_EXTENSION_LENGTH);\n    verifyShortValues(delayTime);\n\n    // Extension inducer (constant).\n    out.put((byte) 0x21);\n    // Graphic control label (constant).\n    out.put((byte) 0xF9);\n    // Block size (constant).\n    out.put((byte) 0x04);\n    // Packed (disposal method, user input, transparent color flag)\n    out.put((byte) 0x00);\n\n    // Frame delay in 100ths of a second.\n    out.putShort((short) delayTime);\n\n    // Transparent color index.\n    out.put((byte) 0x00);\n\n    // Block terminator (constant).\n    out.put((byte) 0x00);\n  }\n\n  private static void verifyRemaining(ByteBuffer buffer, int expected) {\n    if (buffer.remaining() < expected) {\n      throw new IllegalArgumentException(\"Must have at least \" + expected + \" bytes to write\");\n    }\n  }\n\n  private static void verifyShortValues(int... shortValues) {\n    for (int dimen : shortValues) {\n      if (dimen > Short.MAX_VALUE || dimen < 0) {\n        throw new IllegalArgumentException(\n            \"Must pass in non-negative short dimensions, not: \" + dimen);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "third_party/gif_decoder/src/test/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtilTest.java",
    "content": "package com.bumptech.glide.gifdecoder.test;\n\nimport static org.junit.Assert.assertArrayEquals;\n\nimport java.nio.ByteBuffer;\nimport java.util.Arrays;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * Tests for {@link com.bumptech.glide.gifdecoder.test.GifBytesTestUtil}.\n */\n@RunWith(JUnit4.class)\npublic class GifBytesTestUtilTest {\n\n  @Test\n  public void testWriteHeaderAndLsdWithoutGct() {\n    ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH);\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 8, 16, false, 0);\n\n    byte[] expected =\n        new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x08, 0x00, 0x10, 0x20, 0x00, 0x00 };\n\n    assertEquals(expected, buffer);\n  }\n\n  @Test\n  public void testWriteHeaderAndLsdWithGct() {\n    ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH);\n    GifBytesTestUtil.writeHeaderAndLsd(buffer, 8, 16, true, 4);\n\n    byte[] expected =\n        new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x08, 0x00, 0x10, (byte) 0xA4, 0x00,\n            0x00 };\n\n    assertEquals(expected, buffer);\n  }\n\n  @Test\n  public void testWriteImageDescriptorWithoutColorTable() {\n    ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH);\n    GifBytesTestUtil.writeImageDescriptor(buffer, 10, 9, 8, 7, false, 0);\n\n    byte[] expected = new byte[] {\n        // Image separator.\n        0x2C,\n        // Image left.\n        0x00, 0x0A,\n        // Image right.\n        0x00, 0X09,\n        // Image width.\n        0x00, 0x08,\n        // Image height.\n        0x00, 0x07,\n        // Packed field.\n        0x00 };\n\n    assertEquals(expected, buffer);\n  }\n\n  @Test\n  public void testWriteImageDescriptorWithColorTable() {\n    ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH);\n    GifBytesTestUtil.writeImageDescriptor(buffer, 10, 9, 8, 7, true, 4);\n\n    byte packedField =\n        // Set LCT flag\n        (byte) 0x80\n            // Size of color table (2^(N + 1) == 4)\n            | 0x01;\n\n    byte[] expected = new byte[] {\n        // Image separator.\n        0x2C,\n        // Image left.\n        0x00, 0x0A,\n        // Image right.\n        0x00, 0X09,\n        // Image width.\n        0x00, 0x08,\n        // Image height.\n        0x00, 0x07, packedField };\n\n    assertEquals(expected, buffer);\n  }\n\n  @Test\n  public void testWriteColorTable() {\n    final int numColors = 4;\n    ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.getColorTableLength(numColors));\n    GifBytesTestUtil.writeColorTable(buffer, numColors);\n\n    byte[] expected = new byte[] {\n        // First color.\n        0x00, 0x00, 0x00,\n        // Second color.\n        0x00, 0x00, 0x01,\n        // Third color.\n        0x00, 0x00, 0x02,\n        // Fourth color.\n        0x00, 0x00, 0x03, };\n\n\n    assertEquals(expected, buffer);\n  }\n\n  @Test\n  public void testWriteFakeImageData() {\n    ByteBuffer buffer = ByteBuffer.allocate(4);\n    GifBytesTestUtil.writeFakeImageData(buffer, 2);\n\n    byte[] expected = new byte[] { 0x02, 0x01, 0x01, 0x00 };\n\n    assertEquals(expected, buffer);\n  }\n\n  @Test\n  public void testWritesGraphicsControlExtension() {\n    short delay = 20;\n    ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.GRAPHICS_CONTROL_EXTENSION_LENGTH);\n    byte[] expected = new byte[] {\n        // Extension inducer.\n        0x21,\n        // Graphic control label.\n        (byte) 0xF9,\n        // Block size.\n        0x04,\n        // Packed byte.\n        0x00,\n        // Frame delay.\n        0x00, 0x14,\n        // Transparent color index.\n        0x00,\n        // block terminator.\n        0x00 };\n\n    GifBytesTestUtil.writeGraphicsControlExtension(buffer, delay);\n    assertEquals(expected, buffer);\n  }\n\n  private static void assertEquals(byte[] expected, ByteBuffer buffer) {\n    assertArrayEquals(\n        \"expected=\" + Arrays.toString(expected) + \" received=\" + Arrays.toString(buffer.array()),\n        expected, buffer.array());\n  }\n}\n"
  },
  {
    "path": "third_party/gif_encoder/LICENSE",
    "content": "License for AnimatedGifEncoder.java and LZWEncoder.java\n\nNo copyright asserted on the source code of this class. May be used for any\npurpose, however, refer to the Unisys LZW patent for restrictions on use of\nthe associated LZWEncoder class. Please forward any corrections to\nkweiner@fmsware.com.\n\n-----------------------------------------------------------------------------\nLicense for NeuQuant.java\n\nCopyright (c) 1994 Anthony Dekker\n\nNEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See\n\"Kohonen neural networks for optimal colour quantization\" in \"Network:\nComputation in Neural Systems\" Vol. 5 (1994) pp 351-367. for a discussion of\nthe algorithm.\n\nAny party obtaining a copy of these files from the author, directly or\nindirectly, is granted, free of charge, a full and unrestricted irrevocable,\nworld-wide, paid up, royalty-free, nonexclusive right and license to deal in\nthis software and documentation files (the \"Software\"), including without\nlimitation the rights to use, copy, modify, merge, publish, distribute,\nsublicense, and/or sell copies of the Software, and to permit persons who\nreceive copies from any such party to do so, with the only requirement being\nthat this copyright notice remain intact.\n"
  },
  {
    "path": "third_party/gif_encoder/THIRD_PARTY.md",
    "content": "URL: http://java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm\nVersion: Downloaded 9/4/2014.\nLicense: Notice\nLicense File: LICENSE\n\nDescription:\nAndroid port of a GIF encoder.\n\nSee also:\nhttp://members.ozemail.com.au/~dekker/NEUQUANT.HTML\n\nLocal Modifications:\nConverted BufferedImage to Android's Bitmap class, split apart classes into individual files.\nSupport setting transIndex based on the presence of transparent pixels in the Bitmap.\n"
  },
  {
    "path": "third_party/gif_encoder/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"AllowBackup\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java",
    "content": "package com.bumptech.glide.gifencoder;\n\n\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.util.Log;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport java.io.BufferedOutputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more\n * frames.\n *\n * <pre>\n *  Example:\n *     AnimatedGifEncoder e = new AnimatedGifEncoder();\n *     e.start(outputFileName);\n *     e.setDelay(1000);   // 1 frame per sec\n *     e.addFrame(image1);\n *     e.addFrame(image2);\n *     e.addFrame(image3, 100, 100);    // set position of the frame\n *     e.finish();\n * </pre>\n *\n * No copyright asserted on the source code of this class. May be used for any\n * purpose, however, refer to the Unisys LZW patent for restrictions on use of\n * the associated LZWEncoder class. Please forward any corrections to\n * kweiner@fmsware.com.\n *\n * @author Kevin Weiner, FM Software\n * @version 1.03 November 2003\n *\n */\n\npublic class AnimatedGifEncoder {\n    private static final String TAG = \"AnimatedGifEncoder\";\n\n    // The minimum % of an images pixels that must be transparent for us to set a transparent index\n    // automatically.\n    private static final double MIN_TRANSPARENT_PERCENTAGE = 4d;\n\n    private int width; // image size\n\n    private int height;\n\n    private int fixedWidth;   // set by setSize()\n\n    private int fixedHeight;\n\n    private Integer transparent = null; // transparent color if given\n\n    private int transIndex; // transparent index in color table\n\n    private int repeat = -1; // no repeat\n\n    private int delay = 0; // frame delay (hundredths)\n\n    private boolean started = false; // ready to output frames\n\n    private OutputStream out;\n\n    private Bitmap image; // current frame\n\n    private byte[] pixels; // BGR byte array from frame\n\n    private byte[] indexedPixels; // converted frame indexed to palette\n\n    private int colorDepth; // number of bit planes\n\n    private byte[] colorTab; // RGB palette\n\n    private boolean[] usedEntry = new boolean[256]; // active palette entries\n\n    private int palSize = 7; // color table size (bits-1)\n\n    private int dispose = -1; // disposal code (-1 = use default)\n\n    private boolean closeStream = false; // close stream when finished\n\n    private boolean firstFrame = true;\n\n    private boolean sizeSet = false; // if false, get size from first frame\n\n    private int sample = 10; // default sample interval for quantizer\n\n    private boolean hasTransparentPixels;\n\n    /**\n     * Sets the delay time between each frame, or changes it for subsequent frames\n     * (applies to last frame added).\n     *\n     * @param ms\n     *          int delay time in milliseconds\n     */\n    public void setDelay(int ms) {\n        delay = Math.round(ms / 10.0f);\n    }\n\n    /**\n     * Sets the GIF frame disposal code for the last added frame and any\n     * subsequent frames. Default is 0 if no transparent color has been set,\n     * otherwise 2.\n     *\n     * @param code\n     *          int disposal code.\n     */\n    public void setDispose(int code) {\n        if (code >= 0) {\n            dispose = code;\n        }\n    }\n\n    /**\n     * Sets the number of times the set of GIF frames should be played. Default is\n     * 1; 0 means play indefinitely. Must be invoked before the first image is\n     * added.\n     *\n     * @param iter\n     *          int number of iterations.\n     */\n    public void setRepeat(int iter) {\n        if (iter >= 0) {\n            repeat = iter;\n        }\n    }\n\n    /**\n     * Sets the transparent color for the last added frame and any subsequent\n     * frames. Since all colors are subject to modification in the quantization\n     * process, the color in the final palette for each frame closest to the given\n     * color becomes the transparent color for that frame. May be set to null to\n     * indicate no transparent color.\n     *\n     * @param color\n     *          Color to be treated as transparent on display.\n     */\n    public void setTransparent(int color) {\n        transparent = color;\n    }\n\n    /**\n     * Adds next GIF frame. The frame is not written immediately, but is actually\n     * deferred until the next frame is received so that timing data can be\n     * inserted. Invoking <code>finish()</code> flushes all frames. If\n     * <code>setSize</code> was invoked, the size is used for all subsequent frames.\n     * Otherwise, the actual size of the image is used for each frames.\n     *\n     * @param im\n     *          BufferedImage containing frame to write.\n     * @return true if successful.\n     */\n    public boolean addFrame(@Nullable Bitmap im) {\n        return addFrame(im, 0, 0);\n    }\n\n    /**\n     * Adds next GIF frame to the specified position. The frame is not written immediately, but is\n     * actually deferred until the next frame is received so that timing data can be inserted.\n     * Invoking <code>finish()</code> flushes all frames. If <code>setSize</code> was invoked, the\n     * size is used for all subsequent frames. Otherwise, the actual size of the image is used for\n     * each frame.\n     *\n     * See page 11 of http://giflib.sourceforge.net/gif89.txt for the position of the frame\n     *\n     * @param im\n     *          BufferedImage containing frame to write.\n     * @param x\n     *          Column number, in pixels, of the left edge of the image, with respect to the left\n     *          edge of the Logical Screen.\n     * @param y\n     *          Row number, in pixels, of the top edge of the image with respect to the top edge of\n     *          the Logical Screen.\n     * @return true if successful.\n     */\n    public boolean addFrame(@Nullable Bitmap im, int x, int y) {\n        if ((im == null) || !started) {\n            return false;\n        }\n        boolean ok = true;\n        try {\n            if (sizeSet) {\n                setFrameSize(fixedWidth, fixedHeight);\n            } else {\n                setFrameSize(im.getWidth(), im.getHeight());\n            }\n            image = im;\n            getImagePixels(); // convert to correct format if necessary\n            analyzePixels(); // build color table & map pixels\n            if (firstFrame) {\n                writeLSD(); // logical screen descriptor\n                writePalette(); // global color table\n                if (repeat >= 0) {\n                    // use NS app extension to indicate reps\n                    writeNetscapeExt();\n                }\n            }\n            writeGraphicCtrlExt(); // write graphic control extension\n            writeImageDesc(x, y); // image descriptor\n            if (!firstFrame) {\n                writePalette(); // local color table\n            }\n            writePixels(); // encode and write pixel data\n            firstFrame = false;\n        } catch (IOException e) {\n            ok = false;\n        }\n\n        return ok;\n    }\n\n    /**\n     * Flushes any pending data and closes output file. If writing to an\n     * OutputStream, the stream is not closed.\n     */\n    public boolean finish() {\n        if (!started)\n            return false;\n        boolean ok = true;\n        started = false;\n        try {\n            out.write(0x3b); // GIF trailer\n            out.flush();\n            if (closeStream) {\n                out.close();\n            }\n        } catch (IOException e) {\n            ok = false;\n        }\n\n        // reset for subsequent use\n        transIndex = 0;\n        out = null;\n        image = null;\n        pixels = null;\n        indexedPixels = null;\n        colorTab = null;\n        closeStream = false;\n        firstFrame = true;\n\n        return ok;\n    }\n\n    /**\n     * Sets frame rate in frames per second. Equivalent to\n     * <code>setDelay(1000/fps)</code>.\n     *\n     * @param fps\n     *          float frame rate (frames per second)\n     */\n    public void setFrameRate(float fps) {\n        if (fps != 0f) {\n            delay = Math.round(100f / fps);\n        }\n    }\n\n    /**\n     * Sets quality of color quantization (conversion of images to the maximum 256\n     * colors allowed by the GIF specification). Lower values (minimum = 1)\n     * produce better colors, but slow processing significantly. 10 is the\n     * default, and produces good color mapping at reasonable speeds. Values\n     * greater than 20 do not yield significant improvements in speed.\n     *\n     * @param quality int greater than 0.\n     */\n    public void setQuality(int quality) {\n        if (quality < 1)\n            quality = 1;\n        sample = quality;\n    }\n\n    /**\n     * Sets the fixed GIF frame size for all the frames.\n     * This should be called before start.\n     *\n     * @param w\n     *          int frame width.\n     * @param h\n     *          int frame width.\n     */\n    public void setSize(int w, int h) {\n        if (started) {\n            return;\n        }\n\n        fixedWidth = w;\n        fixedHeight = h;\n        if (fixedWidth < 1) {\n            fixedWidth = 320;\n        }\n        if (fixedHeight < 1) {\n            fixedHeight = 240;\n        }\n\n        sizeSet = true;\n    }\n\n    /**\n     * Sets current GIF frame size.\n     *\n     * @param w\n     *          int frame width.\n     * @param h\n     *          int frame width.\n     */\n    private void setFrameSize(int w, int h) {\n        width = w;\n        height = h;\n    }\n\n    /**\n     * Initiates GIF file creation on the given stream. The stream is not closed\n     * automatically.\n     *\n     * @param os\n     *          OutputStream on which GIF images are written.\n     * @return false if initial write failed.\n     */\n    public boolean start(@Nullable OutputStream os) {\n        if (os == null)\n            return false;\n        boolean ok = true;\n        closeStream = false;\n        out = os;\n        try {\n            writeString(\"GIF89a\"); // header\n        } catch (IOException e) {\n            ok = false;\n        }\n        return started = ok;\n    }\n\n    /**\n     * Initiates writing of a GIF file with the specified name.\n     *\n     * @param file\n     *          String containing output file name.\n     * @return false if open or initial write failed.\n     */\n    public boolean start(@NonNull String file) {\n        boolean ok;\n        try {\n            out = new BufferedOutputStream(new FileOutputStream(file));\n            ok = start(out);\n            closeStream = true;\n        } catch (IOException e) {\n            ok = false;\n        }\n        return started = ok;\n    }\n\n    /**\n     * Analyzes image colors and creates color map.\n     */\n    private void analyzePixels() {\n        int len = pixels.length;\n        int nPix = len / 3;\n        indexedPixels = new byte[nPix];\n        NeuQuant nq = new NeuQuant(pixels, len, sample);\n        // initialize quantizer\n        colorTab = nq.process(); // create reduced palette\n        // convert map from BGR to RGB\n        for (int i = 0; i < colorTab.length; i += 3) {\n            byte temp = colorTab[i];\n            colorTab[i] = colorTab[i + 2];\n            colorTab[i + 2] = temp;\n            usedEntry[i / 3] = false;\n        }\n        // map image pixels to new palette\n        int k = 0;\n        for (int i = 0; i < nPix; i++) {\n            int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);\n            usedEntry[index] = true;\n            indexedPixels[i] = (byte) index;\n        }\n        pixels = null;\n        colorDepth = 8;\n        palSize = 7;\n        // get closest match to transparent color if specified\n        if (transparent != null) {\n            transIndex = findClosest(transparent);\n        } else if (hasTransparentPixels) {\n            transIndex = findClosest(Color.TRANSPARENT);\n        }\n    }\n\n    /**\n     * Returns index of palette color closest to c\n     *\n     */\n    private int findClosest(int color) {\n        if (colorTab == null)\n            return -1;\n        int r = Color.red(color);\n        int g = Color.green(color);\n        int b = Color.blue(color);\n        int minpos = 0;\n        int dmin = 256 * 256 * 256;\n        int len = colorTab.length;\n        for (int i = 0; i < len;) {\n            int dr = r - (colorTab[i++] & 0xff);\n            int dg = g - (colorTab[i++] & 0xff);\n            int db = b - (colorTab[i] & 0xff);\n            int d = dr * dr + dg * dg + db * db;\n            int index = i / 3;\n            if (usedEntry[index] && (d < dmin)) {\n                dmin = d;\n                minpos = index;\n            }\n            i++;\n        }\n        return minpos;\n    }\n\n    /**\n     * Extracts image pixels into byte array \"pixels\"\n     */\n    private void getImagePixels() {\n        int w = image.getWidth();\n        int h = image.getHeight();\n\n        if ((w != width) || (h != height)) {\n            // create new image with right size/format\n            Bitmap temp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n            Canvas canvas = new Canvas(temp);\n            canvas.drawBitmap(temp, 0, 0, null);\n            image = temp;\n        }\n        int[] pixelsInt = new int[w * h];\n        image.getPixels(pixelsInt, 0, w, 0, 0, w, h);\n\n        // The algorithm requires 3 bytes per pixel as RGB.\n        pixels = new byte[pixelsInt.length * 3];\n\n        int pixelsIndex = 0;\n        hasTransparentPixels = false;\n        int totalTransparentPixels = 0;\n        for (final int pixel : pixelsInt) {\n            if (pixel == Color.TRANSPARENT) {\n                totalTransparentPixels++;\n            }\n            pixels[pixelsIndex++] = (byte) (pixel & 0xFF);\n            pixels[pixelsIndex++] = (byte) ((pixel >> 8) & 0xFF);\n            pixels[pixelsIndex++] = (byte) ((pixel >> 16) & 0xFF);\n        }\n\n        double transparentPercentage = 100 * totalTransparentPixels / (double) pixelsInt.length;\n        // Assume images with greater where more than n% of the pixels are transparent actually have\n        // transparency. See issue #214.\n        hasTransparentPixels = transparentPercentage > MIN_TRANSPARENT_PERCENTAGE;\n        if (Log.isLoggable(TAG, Log.DEBUG)) {\n            Log.d(TAG, \"got pixels for frame with \" + transparentPercentage\n                + \"% transparent pixels\");\n        }\n    }\n\n    /**\n     * Writes Graphic Control Extension\n     */\n    private void writeGraphicCtrlExt() throws IOException {\n        out.write(0x21); // extension introducer\n        out.write(0xf9); // GCE label\n        out.write(4); // data block size\n        int transp, disp;\n        if (transparent == null && !hasTransparentPixels) {\n            transp = 0;\n            disp = 0; // dispose = no action\n        } else {\n            transp = 1;\n            disp = 2; // force clear if using transparent color\n        }\n        if (dispose >= 0) {\n            disp = dispose & 7; // user override\n        }\n        disp <<= 2;\n\n        // packed fields\n        out.write(0 | // 1:3 reserved\n                disp | // 4:6 disposal\n                0 | // 7 user input - 0 = none\n                transp); // 8 transparency flag\n\n        writeShort(delay); // delay x 1/100 sec\n        out.write(transIndex); // transparent color index\n        out.write(0); // block terminator\n    }\n\n    /**\n     * Writes Image Descriptor\n     */\n    private void writeImageDesc(int x, int y) throws IOException {\n        out.write(0x2c); // image separator\n        writeShort(x); // image position\n        writeShort(y);\n        writeShort(width); // image size\n        writeShort(height);\n        // packed fields\n        if (firstFrame) {\n            // no LCT - GCT is used for first (or only) frame\n            out.write(0);\n        } else {\n            // specify normal LCT\n            out.write(0x80 | // 1 local color table 1=yes\n                    0 | // 2 interlace - 0=no\n                    0 | // 3 sorted - 0=no\n                    0 | // 4-5 reserved\n                    palSize); // 6-8 size of color table\n        }\n    }\n\n    /**\n     * Writes Logical Screen Descriptor\n     */\n    private void writeLSD() throws IOException {\n        // logical screen size\n        writeShort(width);\n        writeShort(height);\n        // packed fields\n        out.write((0x80 | // 1 : global color table flag = 1 (gct used)\n                0x70 | // 2-4 : color resolution = 7\n                0x00 | // 5 : gct sort flag = 0\n                palSize)); // 6-8 : gct size\n\n        out.write(0); // background color index\n        out.write(0); // pixel aspect ratio - assume 1:1\n    }\n\n    /**\n     * Writes Netscape application extension to define repeat count.\n     */\n    private void writeNetscapeExt() throws IOException {\n        out.write(0x21); // extension introducer\n        out.write(0xff); // app extension label\n        out.write(11); // block size\n        writeString(\"NETSCAPE\" + \"2.0\"); // app id + auth code\n        out.write(3); // sub-block size\n        out.write(1); // loop sub-block id\n        writeShort(repeat); // loop count (extra iterations, 0=repeat forever)\n        out.write(0); // block terminator\n    }\n\n    /**\n     * Writes color table\n     */\n    private void writePalette() throws IOException {\n        out.write(colorTab, 0, colorTab.length);\n        int n = (3 * 256) - colorTab.length;\n        for (int i = 0; i < n; i++) {\n            out.write(0);\n        }\n    }\n\n    /**\n     * Encodes and writes pixel data\n     */\n    private void writePixels() throws IOException {\n        LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);\n        encoder.encode(out);\n    }\n\n    /**\n     * Write 16-bit value to output stream, LSB first\n     */\n    private void writeShort(int value) throws IOException {\n        out.write(value & 0xff);\n        out.write((value >> 8) & 0xff);\n    }\n\n    /**\n     * Writes string to output stream\n     */\n    private void writeString(String s) throws IOException {\n        for (int i = 0; i < s.length(); i++) {\n            out.write((byte) s.charAt(i));\n        }\n    }\n}\n"
  },
  {
    "path": "third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/LZWEncoder.java",
    "content": "package com.bumptech.glide.gifencoder;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n// ==============================================================================\n// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.\n// K Weiner 12/00\nclass LZWEncoder {\n\n    private static final int EOF = -1;\n\n    private int imgW, imgH;\n\n    private byte[] pixAry;\n\n    private int initCodeSize;\n\n    private int remaining;\n\n    private int curPixel;\n\n    // GIFCOMPR.C - GIF Image compression routines\n    //\n    // Lempel-Ziv compression based on 'compress'. GIF modifications by\n    // David Rowley (mgardi@watdcsu.waterloo.edu)\n\n    // General DEFINEs\n\n    static final int BITS = 12;\n\n    static final int HSIZE = 5003; // 80% occupancy\n\n    // GIF Image compression - modified 'compress'\n    //\n    // Based on: compress.c - File compression ala IEEE Computer, June 1984.\n    //\n    // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)\n    // Jim McKie (decvax!mcvax!jim)\n    // Steve Davies (decvax!vax135!petsd!peora!srd)\n    // Ken Turkowski (decvax!decwrl!turtlevax!ken)\n    // James A. Woods (decvax!ihnp4!ames!jaw)\n    // Joe Orost (decvax!vax135!petsd!joe)\n\n    int n_bits; // number of bits/code\n\n    int maxbits = BITS; // user settable max # bits/code\n\n    int maxcode; // maximum code, given n_bits\n\n    int maxmaxcode = 1 << BITS; // should NEVER generate this code\n\n    int[] htab = new int[HSIZE];\n\n    int[] codetab = new int[HSIZE];\n\n    int hsize = HSIZE; // for dynamic table sizing\n\n    int free_ent = 0; // first unused entry\n\n    // block compression parameters -- after all codes are used up,\n    // and compression rate changes, start over.\n    boolean clear_flg = false;\n\n    // Algorithm: use open addressing double hashing (no chaining) on the\n    // prefix code / next character combination. We do a variant of Knuth's\n    // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime\n    // secondary probe. Here, the modular division first probe is gives way\n    // to a faster exclusive-or manipulation. Also do block compression with\n    // an adaptive reset, whereby the code table is cleared when the compression\n    // ratio decreases, but after the table fills. The variable-length output\n    // codes are re-sized at this point, and a special CLEAR code is generated\n    // for the decompressor. Late addition: construct the table according to\n    // file size for noticeable speed improvement on small files. Please direct\n    // questions about this implementation to ames!jaw.\n\n    int g_init_bits;\n\n    int ClearCode;\n\n    int EOFCode;\n\n    // output\n    //\n    // Output the given code.\n    // Inputs:\n    // code: A n_bits-bit integer. If == -1, then EOF. This assumes\n    // that n_bits =< wordsize - 1.\n    // Outputs:\n    // Outputs code to the file.\n    // Assumptions:\n    // Chars are 8 bits long.\n    // Algorithm:\n    // Maintain a BITS character long buffer (so that 8 codes will\n    // fit in it exactly). Use the VAX insv instruction to insert each\n    // code in turn. When the buffer fills up empty it and start over.\n\n    int cur_accum = 0;\n\n    int cur_bits = 0;\n\n    int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF,\n            0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};\n\n    // Number of characters so far in this 'packet'\n    int a_count;\n\n    // Define the storage for the packet accumulator\n    byte[] accum = new byte[256];\n\n    // ----------------------------------------------------------------------------\n    LZWEncoder(int width, int height, byte[] pixels, int color_depth) {\n        imgW = width;\n        imgH = height;\n        pixAry = pixels;\n        initCodeSize = Math.max(2, color_depth);\n    }\n\n    // Add a character to the end of the current packet, and if it is 254\n    // characters, flush the packet to disk.\n    void char_out(byte c, OutputStream outs) throws IOException {\n        accum[a_count++] = c;\n        if (a_count >= 254)\n            flush_char(outs);\n    }\n\n    // Clear out the hash table\n\n    // table clear for block compress\n    void cl_block(OutputStream outs) throws IOException {\n        cl_hash(hsize);\n        free_ent = ClearCode + 2;\n        clear_flg = true;\n\n        output(ClearCode, outs);\n    }\n\n    // reset code table\n    void cl_hash(int hsize) {\n        for (int i = 0; i < hsize; ++i)\n            htab[i] = -1;\n    }\n\n    void compress(int init_bits, OutputStream outs) throws IOException {\n        int fcode;\n        int i /* = 0 */;\n        int c;\n        int ent;\n        int disp;\n        int hsize_reg;\n        int hshift;\n\n        // Set up the globals: g_init_bits - initial number of bits\n        g_init_bits = init_bits;\n\n        // Set up the necessary values\n        clear_flg = false;\n        n_bits = g_init_bits;\n        maxcode = MAXCODE(n_bits);\n\n        ClearCode = 1 << (init_bits - 1);\n        EOFCode = ClearCode + 1;\n        free_ent = ClearCode + 2;\n\n        a_count = 0; // clear packet\n\n        ent = nextPixel();\n\n        hshift = 0;\n        for (fcode = hsize; fcode < 65536; fcode *= 2)\n            ++hshift;\n        hshift = 8 - hshift; // set hash code range bound\n\n        hsize_reg = hsize;\n        cl_hash(hsize_reg); // clear hash table\n\n        output(ClearCode, outs);\n\n        outer_loop:\n        while ((c = nextPixel()) != EOF) {\n            fcode = (c << maxbits) + ent;\n            i = (c << hshift) ^ ent; // xor hashing\n\n            if (htab[i] == fcode) {\n                ent = codetab[i];\n                continue;\n            } else if (htab[i] >= 0) // non-empty slot\n            {\n                disp = hsize_reg - i; // secondary hash (after G. Knott)\n                if (i == 0)\n                    disp = 1;\n                do {\n                    if ((i -= disp) < 0)\n                        i += hsize_reg;\n\n                    if (htab[i] == fcode) {\n                        ent = codetab[i];\n                        continue outer_loop;\n                    }\n                } while (htab[i] >= 0);\n            }\n            output(ent, outs);\n            ent = c;\n            if (free_ent < maxmaxcode) {\n                codetab[i] = free_ent++; // code -> hashtable\n                htab[i] = fcode;\n            } else\n                cl_block(outs);\n        }\n        // Put out the final code.\n        output(ent, outs);\n        output(EOFCode, outs);\n    }\n\n    // ----------------------------------------------------------------------------\n    void encode(OutputStream os) throws IOException {\n        os.write(initCodeSize); // write \"initial code size\" byte\n\n        remaining = imgW * imgH; // reset navigation variables\n        curPixel = 0;\n\n        compress(initCodeSize + 1, os); // compress and write the pixel data\n\n        os.write(0); // write block terminator\n    }\n\n    // Flush the packet to disk, and reset the accumulator\n    void flush_char(OutputStream outs) throws IOException {\n        if (a_count > 0) {\n            outs.write(a_count);\n            outs.write(accum, 0, a_count);\n            a_count = 0;\n        }\n    }\n\n    final int MAXCODE(int n_bits) {\n        return (1 << n_bits) - 1;\n    }\n\n    // ----------------------------------------------------------------------------\n    // Return the next pixel from the image\n    // ----------------------------------------------------------------------------\n    private int nextPixel() {\n        if (remaining == 0)\n            return EOF;\n\n        --remaining;\n\n        byte pix = pixAry[curPixel++];\n\n        return pix & 0xff;\n    }\n\n    void output(int code, OutputStream outs) throws IOException {\n        cur_accum &= masks[cur_bits];\n\n        if (cur_bits > 0)\n            cur_accum |= (code << cur_bits);\n        else\n            cur_accum = code;\n\n        cur_bits += n_bits;\n\n        while (cur_bits >= 8) {\n            char_out((byte) (cur_accum & 0xff), outs);\n            cur_accum >>= 8;\n            cur_bits -= 8;\n        }\n\n        // If the next entry is going to be too big for the code size,\n        // then increase it, if possible.\n        if (free_ent > maxcode || clear_flg) {\n            if (clear_flg) {\n                maxcode = MAXCODE(n_bits = g_init_bits);\n                clear_flg = false;\n            } else {\n                ++n_bits;\n                if (n_bits == maxbits)\n                    maxcode = maxmaxcode;\n                else\n                    maxcode = MAXCODE(n_bits);\n            }\n        }\n\n        if (code == EOFCode) {\n            // At EOF, write the rest of the buffer.\n            while (cur_bits > 0) {\n                char_out((byte) (cur_accum & 0xff), outs);\n                cur_accum >>= 8;\n                cur_bits -= 8;\n            }\n\n            flush_char(outs);\n        }\n    }\n}\n"
  },
  {
    "path": "third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/NeuQuant.java",
    "content": "package com.bumptech.glide.gifencoder;\n\n/*\n * NeuQuant Neural-Net Quantization Algorithm\n * ------------------------------------------\n *\n * Copyright (c) 1994 Anthony Dekker\n *\n * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See\n * \"Kohonen neural networks for optimal colour quantization\" in \"Network:\n * Computation in Neural Systems\" Vol. 5 (1994) pp 351-367. for a discussion of\n * the algorithm.\n *\n * Any party obtaining a copy of these files from the author, directly or\n * indirectly, is granted, free of charge, a full and unrestricted irrevocable,\n * world-wide, paid up, royalty-free, nonexclusive right and license to deal in\n * this software and documentation files (the \"Software\"), including without\n * limitation the rights to use, copy, modify, merge, publish, distribute,\n * sublicense, and/or sell copies of the Software, and to permit persons who\n * receive copies from any such party to do so, with the only requirement being\n * that this copyright notice remain intact.\n */\n\n// Ported to Java 12/00 K Weiner\nclass NeuQuant {\n\n    protected static final int netsize = 256; /* number of colours used */\n\n    /* four primes near 500 - assume no image has a length so large */\n  /* that it is divisible by all four primes */\n    protected static final int prime1 = 499;\n\n    protected static final int prime2 = 491;\n\n    protected static final int prime3 = 487;\n\n    protected static final int prime4 = 503;\n\n    protected static final int minpicturebytes = (3 * prime4);\n\n  /* minimum size for input image */\n\n  /*\n   * Program Skeleton ---------------- [select samplefac in range 1..30] [read\n   * image from input file] pic = (unsigned char*) malloc(3*width*height);\n   * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output\n   * image header, using writecolourmap(f)] inxbuild(); write output image using\n   * inxsearch(b,g,r)\n   */\n\n  /*\n   * Network Definitions -------------------\n   */\n\n    protected static final int maxnetpos = (netsize - 1);\n\n    protected static final int netbiasshift = 4; /* bias for colour values */\n\n    protected static final int ncycles = 100; /* no. of learning cycles */\n\n    /* defs for freq and bias */\n    protected static final int intbiasshift = 16; /* bias for fractions */\n\n    protected static final int intbias = (((int) 1) << intbiasshift);\n\n    protected static final int gammashift = 10; /* gamma = 1024 */\n\n    protected static final int gamma = (((int) 1) << gammashift);\n\n    protected static final int betashift = 10;\n\n    protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */\n\n    protected static final int betagamma = (intbias << (gammashift - betashift));\n\n    /* defs for decreasing radius factor */\n    protected static final int initrad = (netsize >> 3); /*\n                                                         * for 256 cols, radius\n                                                         * starts\n                                                         */\n\n    protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */\n\n    protected static final int radiusbias = (((int) 1) << radiusbiasshift);\n\n    protected static final int initradius = (initrad * radiusbias); /*\n                                                                   * and\n                                                                   * decreases\n                                                                   * by a\n                                                                   */\n\n    protected static final int radiusdec = 30; /* factor of 1/30 each cycle */\n\n    /* defs for decreasing alpha factor */\n    protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */\n\n    protected static final int initalpha = (((int) 1) << alphabiasshift);\n\n    protected int alphadec; /* biased by 10 bits */\n\n    /* radbias and alpharadbias used for radpower calculation */\n    protected static final int radbiasshift = 8;\n\n    protected static final int radbias = (((int) 1) << radbiasshift);\n\n    protected static final int alpharadbshift = (alphabiasshift + radbiasshift);\n\n    protected static final int alpharadbias = (((int) 1) << alpharadbshift);\n\n  /*\n   * Types and Global Variables --------------------------\n   */\n\n    protected byte[] thepicture; /* the input image itself */\n\n    protected int lengthcount; /* lengthcount = H*W*3 */\n\n    protected int samplefac; /* sampling factor 1..30 */\n\n    // typedef int pixel[4]; /* BGRc */\n    protected int[][] network; /* the network itself - [netsize][4] */\n\n    protected int[] netindex = new int[256];\n\n  /* for network lookup - really 256 */\n\n    protected int[] bias = new int[netsize];\n\n    /* bias and freq arrays for learning */\n    protected int[] freq = new int[netsize];\n\n    protected int[] radpower = new int[initrad];\n\n  /* radpower for precomputation */\n\n    /*\n     * Initialise network in range (0,0,0) to (255,255,255) and set parameters\n     * -----------------------------------------------------------------------\n     */\n    public NeuQuant(byte[] thepic, int len, int sample) {\n\n        int i;\n        int[] p;\n\n        thepicture = thepic;\n        lengthcount = len;\n        samplefac = sample;\n\n        network = new int[netsize][];\n        for (i = 0; i < netsize; i++) {\n            network[i] = new int[4];\n            p = network[i];\n            p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;\n            freq[i] = intbias / netsize; /* 1/netsize */\n            bias[i] = 0;\n        }\n    }\n\n    public byte[] colorMap() {\n        byte[] map = new byte[3 * netsize];\n        int[] index = new int[netsize];\n        for (int i = 0; i < netsize; i++)\n            index[network[i][3]] = i;\n        int k = 0;\n        for (int i = 0; i < netsize; i++) {\n            int j = index[i];\n            map[k++] = (byte) (network[j][0]);\n            map[k++] = (byte) (network[j][1]);\n            map[k++] = (byte) (network[j][2]);\n        }\n        return map;\n    }\n\n    /*\n     * Insertion sort of network and building of netindex[0..255] (to do after\n     * unbias)\n     * -------------------------------------------------------------------------------\n     */\n    public void inxbuild() {\n\n        int i, j, smallpos, smallval;\n        int[] p;\n        int[] q;\n        int previouscol, startpos;\n\n        previouscol = 0;\n        startpos = 0;\n        for (i = 0; i < netsize; i++) {\n            p = network[i];\n            smallpos = i;\n            smallval = p[1]; /* index on g */\n      /* find smallest in i..netsize-1 */\n            for (j = i + 1; j < netsize; j++) {\n                q = network[j];\n                if (q[1] < smallval) { /* index on g */\n                    smallpos = j;\n                    smallval = q[1]; /* index on g */\n                }\n            }\n            q = network[smallpos];\n      /* swap p (i) and q (smallpos) entries */\n            if (i != smallpos) {\n                j = q[0];\n                q[0] = p[0];\n                p[0] = j;\n                j = q[1];\n                q[1] = p[1];\n                p[1] = j;\n                j = q[2];\n                q[2] = p[2];\n                p[2] = j;\n                j = q[3];\n                q[3] = p[3];\n                p[3] = j;\n            }\n      /* smallval entry is now in position i */\n            if (smallval != previouscol) {\n                netindex[previouscol] = (startpos + i) >> 1;\n                for (j = previouscol + 1; j < smallval; j++)\n                    netindex[j] = i;\n                previouscol = smallval;\n                startpos = i;\n            }\n        }\n        netindex[previouscol] = (startpos + maxnetpos) >> 1;\n        for (j = previouscol + 1; j < 256; j++)\n            netindex[j] = maxnetpos; /* really 256 */\n    }\n\n    /*\n     * Main Learning Loop ------------------\n     */\n    public void learn() {\n\n        int i, j, b, g, r;\n        int radius, rad, alpha, step, delta, samplepixels;\n        byte[] p;\n        int pix, lim;\n\n        if (lengthcount < minpicturebytes)\n            samplefac = 1;\n        alphadec = 30 + ((samplefac - 1) / 3);\n        p = thepicture;\n        pix = 0;\n        lim = lengthcount;\n        samplepixels = lengthcount / (3 * samplefac);\n        delta = samplepixels / ncycles;\n        alpha = initalpha;\n        radius = initradius;\n\n        rad = radius >> radiusbiasshift;\n        if (rad <= 1)\n            rad = 0;\n        for (i = 0; i < rad; i++)\n            radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));\n\n        // fprintf(stderr,\"beginning 1D learning: initial radius=%d\\n\", rad);\n\n        if (lengthcount < minpicturebytes)\n            step = 3;\n        else if ((lengthcount % prime1) != 0)\n            step = 3 * prime1;\n        else {\n            if ((lengthcount % prime2) != 0)\n                step = 3 * prime2;\n            else {\n                if ((lengthcount % prime3) != 0)\n                    step = 3 * prime3;\n                else\n                    step = 3 * prime4;\n            }\n        }\n\n        i = 0;\n        while (i < samplepixels) {\n            b = (p[pix + 0] & 0xff) << netbiasshift;\n            g = (p[pix + 1] & 0xff) << netbiasshift;\n            r = (p[pix + 2] & 0xff) << netbiasshift;\n            j = contest(b, g, r);\n\n            altersingle(alpha, j, b, g, r);\n            if (rad != 0)\n                alterneigh(rad, j, b, g, r); /* alter neighbours */\n\n            pix += step;\n            if (pix >= lim)\n                pix -= lengthcount;\n\n            i++;\n            if (delta == 0)\n                delta = 1;\n            if (i % delta == 0) {\n                alpha -= alpha / alphadec;\n                radius -= radius / radiusdec;\n                rad = radius >> radiusbiasshift;\n                if (rad <= 1)\n                    rad = 0;\n                for (j = 0; j < rad; j++)\n                    radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));\n            }\n        }\n        // fprintf(stderr,\"finished 1D learning: final alpha=%f\n        // !\\n\",((float)alpha)/initalpha);\n    }\n\n    /*\n     * Search for BGR values 0..255 (after net is unbiased) and return colour\n     * index\n     * ----------------------------------------------------------------------------\n     */\n    public int map(int b, int g, int r) {\n\n        int i, j, dist, a, bestd;\n        int[] p;\n        int best;\n\n        bestd = 1000; /* biggest possible dist is 256*3 */\n        best = -1;\n        i = netindex[g]; /* index on g */\n        j = i - 1; /* start at netindex[g] and work outwards */\n\n        while ((i < netsize) || (j >= 0)) {\n            if (i < netsize) {\n                p = network[i];\n                dist = p[1] - g; /* inx key */\n                if (dist >= bestd)\n                    i = netsize; /* stop iter */\n                else {\n                    i++;\n                    if (dist < 0)\n                        dist = -dist;\n                    a = p[0] - b;\n                    if (a < 0)\n                        a = -a;\n                    dist += a;\n                    if (dist < bestd) {\n                        a = p[2] - r;\n                        if (a < 0)\n                            a = -a;\n                        dist += a;\n                        if (dist < bestd) {\n                            bestd = dist;\n                            best = p[3];\n                        }\n                    }\n                }\n            }\n            if (j >= 0) {\n                p = network[j];\n                dist = g - p[1]; /* inx key - reverse dif */\n                if (dist >= bestd)\n                    j = -1; /* stop iter */\n                else {\n                    j--;\n                    if (dist < 0)\n                        dist = -dist;\n                    a = p[0] - b;\n                    if (a < 0)\n                        a = -a;\n                    dist += a;\n                    if (dist < bestd) {\n                        a = p[2] - r;\n                        if (a < 0)\n                            a = -a;\n                        dist += a;\n                        if (dist < bestd) {\n                            bestd = dist;\n                            best = p[3];\n                        }\n                    }\n                }\n            }\n        }\n        return (best);\n    }\n\n    public byte[] process() {\n        learn();\n        unbiasnet();\n        inxbuild();\n        return colorMap();\n    }\n\n    /*\n     * Unbias network to give byte values 0..255 and record position i to prepare\n     * for sort\n     * -----------------------------------------------------------------------------------\n     */\n    public void unbiasnet() {\n\n        int i, j;\n\n        for (i = 0; i < netsize; i++) {\n            network[i][0] >>= netbiasshift;\n            network[i][1] >>= netbiasshift;\n            network[i][2] >>= netbiasshift;\n            network[i][3] = i; /* record colour no */\n        }\n    }\n\n    /*\n     * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in\n     * radpower[|i-j|]\n     * ---------------------------------------------------------------------------------\n     */\n    protected void alterneigh(int rad, int i, int b, int g, int r) {\n\n        int j, k, lo, hi, a, m;\n        int[] p;\n\n        lo = i - rad;\n        if (lo < -1)\n            lo = -1;\n        hi = i + rad;\n        if (hi > netsize)\n            hi = netsize;\n\n        j = i + 1;\n        k = i - 1;\n        m = 1;\n        while ((j < hi) || (k > lo)) {\n            a = radpower[m++];\n            if (j < hi) {\n                p = network[j++];\n                try {\n                    p[0] -= (a * (p[0] - b)) / alpharadbias;\n                    p[1] -= (a * (p[1] - g)) / alpharadbias;\n                    p[2] -= (a * (p[2] - r)) / alpharadbias;\n                } catch (Exception e) {\n                } // prevents 1.3 miscompilation\n            }\n            if (k > lo) {\n                p = network[k--];\n                try {\n                    p[0] -= (a * (p[0] - b)) / alpharadbias;\n                    p[1] -= (a * (p[1] - g)) / alpharadbias;\n                    p[2] -= (a * (p[2] - r)) / alpharadbias;\n                } catch (Exception e) {\n                }\n            }\n        }\n    }\n\n    /*\n     * Move neuron i towards biased (b,g,r) by factor alpha\n     * ----------------------------------------------------\n     */\n    protected void altersingle(int alpha, int i, int b, int g, int r) {\n\n    /* alter hit neuron */\n        int[] n = network[i];\n        n[0] -= (alpha * (n[0] - b)) / initalpha;\n        n[1] -= (alpha * (n[1] - g)) / initalpha;\n        n[2] -= (alpha * (n[2] - r)) / initalpha;\n    }\n\n    /*\n     * Search for biased BGR values ----------------------------\n     */\n    protected int contest(int b, int g, int r) {\n\n    /* finds closest neuron (min dist) and updates freq */\n    /* finds best neuron (min dist-bias) and returns position */\n    /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */\n    /* bias[i] = gamma*((1/netsize)-freq[i]) */\n\n        int i, dist, a, biasdist, betafreq;\n        int bestpos, bestbiaspos, bestd, bestbiasd;\n        int[] n;\n\n        bestd = ~(((int) 1) << 31);\n        bestbiasd = bestd;\n        bestpos = -1;\n        bestbiaspos = bestpos;\n\n        for (i = 0; i < netsize; i++) {\n            n = network[i];\n            dist = n[0] - b;\n            if (dist < 0)\n                dist = -dist;\n            a = n[1] - g;\n            if (a < 0)\n                a = -a;\n            dist += a;\n            a = n[2] - r;\n            if (a < 0)\n                a = -a;\n            dist += a;\n            if (dist < bestd) {\n                bestd = dist;\n                bestpos = i;\n            }\n            biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));\n            if (biasdist < bestbiasd) {\n                bestbiasd = biasdist;\n                bestbiaspos = i;\n            }\n            betafreq = (freq[i] >> betashift);\n            freq[i] -= betafreq;\n            bias[i] += (betafreq << gammashift);\n        }\n        freq[bestpos] += beta;\n        bias[bestpos] -= betagamma;\n        return (bestbiaspos);\n    }\n}\n"
  },
  {
    "path": "third_party/gradle.properties",
    "content": "# Prefix and postfix for source and javadoc jars.\nJAR_PREFIX=glide-\nJAR_POSTFIX=-thirdparty\n"
  }
]