Full Code of zhihu/Matisse for AI

master 21591aebe73a cached
123 files
334.1 KB
79.3k tokens
438 symbols
1 requests
Download .txt
Showing preview only (369K chars total). Download the full file or copy to clipboard to get everything.
Repository: zhihu/Matisse
Branch: master
Commit: 21591aebe73a
Files: 123
Total size: 334.1 KB

Directory structure:
gitextract_8rhgqmr3/

├── .github/
│   └── ISSUE_TEMPLATE/
│       └── bug_report.md
├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── build.gradle
├── checkstyle.xml
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── matisse/
│   ├── build.gradle
│   ├── gradle.properties
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── zhihu/
│           │           └── matisse/
│           │               ├── Matisse.java
│           │               ├── MimeType.java
│           │               ├── SelectionCreator.java
│           │               ├── engine/
│           │               │   ├── ImageEngine.java
│           │               │   └── impl/
│           │               │       ├── GlideEngine.java
│           │               │       └── PicassoEngine.java
│           │               ├── filter/
│           │               │   └── Filter.java
│           │               ├── internal/
│           │               │   ├── entity/
│           │               │   │   ├── Album.java
│           │               │   │   ├── CaptureStrategy.java
│           │               │   │   ├── IncapableCause.java
│           │               │   │   ├── Item.java
│           │               │   │   └── SelectionSpec.java
│           │               │   ├── loader/
│           │               │   │   ├── AlbumLoader.java
│           │               │   │   └── AlbumMediaLoader.java
│           │               │   ├── model/
│           │               │   │   ├── AlbumCollection.java
│           │               │   │   ├── AlbumMediaCollection.java
│           │               │   │   └── SelectedItemCollection.java
│           │               │   ├── ui/
│           │               │   │   ├── AlbumPreviewActivity.java
│           │               │   │   ├── BasePreviewActivity.java
│           │               │   │   ├── MediaSelectionFragment.java
│           │               │   │   ├── PreviewItemFragment.java
│           │               │   │   ├── SelectedPreviewActivity.java
│           │               │   │   ├── adapter/
│           │               │   │   │   ├── AlbumMediaAdapter.java
│           │               │   │   │   ├── AlbumsAdapter.java
│           │               │   │   │   ├── PreviewPagerAdapter.java
│           │               │   │   │   └── RecyclerViewCursorAdapter.java
│           │               │   │   └── widget/
│           │               │   │       ├── AlbumsSpinner.java
│           │               │   │       ├── CheckRadioView.java
│           │               │   │       ├── CheckView.java
│           │               │   │       ├── IncapableDialog.java
│           │               │   │       ├── MediaGrid.java
│           │               │   │       ├── MediaGridInset.java
│           │               │   │       ├── PreviewViewPager.java
│           │               │   │       ├── RoundedRectangleImageView.java
│           │               │   │       └── SquareFrameLayout.java
│           │               │   └── utils/
│           │               │       ├── ExifInterfaceCompat.java
│           │               │       ├── MediaStoreCompat.java
│           │               │       ├── PathUtils.java
│           │               │       ├── PhotoMetadataUtils.java
│           │               │       ├── Platform.java
│           │               │       ├── SingleMediaScanner.java
│           │               │       └── UIUtils.java
│           │               ├── listener/
│           │               │   ├── OnCheckedListener.java
│           │               │   ├── OnFragmentInteractionListener.java
│           │               │   └── OnSelectedListener.java
│           │               └── ui/
│           │                   └── MatisseActivity.java
│           └── res/
│               ├── color/
│               │   ├── dracula_bottom_toolbar_apply.xml
│               │   ├── dracula_bottom_toolbar_preview.xml
│               │   ├── dracula_preview_bottom_toolbar_apply.xml
│               │   ├── zhihu_bottom_toolbar_apply.xml
│               │   ├── zhihu_bottom_toolbar_preview.xml
│               │   └── zhihu_preview_bottom_toolbar_apply.xml
│               ├── layout/
│               │   ├── activity_matisse.xml
│               │   ├── activity_media_preview.xml
│               │   ├── album_list_item.xml
│               │   ├── fragment_media_selection.xml
│               │   ├── fragment_preview_item.xml
│               │   ├── media_grid_content.xml
│               │   ├── media_grid_item.xml
│               │   └── photo_capture_item.xml
│               ├── values/
│               │   ├── attrs.xml
│               │   ├── colors.xml
│               │   ├── colors_dracula.xml
│               │   ├── colors_zhihu.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               ├── values-ar/
│               │   └── strings.xml
│               ├── values-ca/
│               │   └── strings.xml
│               ├── values-de/
│               │   └── strings.xml
│               ├── values-es/
│               │   └── strings.xml
│               ├── values-it/
│               │   └── strings.xml
│               ├── values-ko/
│               │   └── strings.xml
│               ├── values-pl/
│               │   └── strings.xml
│               ├── values-pt-rBR/
│               │   └── strings.xml
│               ├── values-ru/
│               │   └── strings.xml
│               ├── values-tr-rTR/
│               │   └── strings.xml
│               ├── values-uk/
│               │   └── strings.xml
│               ├── values-vi/
│               │   └── strings.xml
│               ├── values-zh/
│               │   └── strings.xml
│               └── values-zh-rTW/
│                   └── strings.xml
├── sample/
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── zhihu/
│           │           └── matisse/
│           │               └── sample/
│           │                   ├── GifSizeFilter.java
│           │                   └── SampleActivity.java
│           └── res/
│               ├── layout/
│               │   ├── activity_main.xml
│               │   └── uri_item.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               ├── values-ca/
│               │   └── strings.xml
│               ├── values-es/
│               │   └── strings.xml
│               ├── values-it/
│               │   └── strings.xml
│               ├── values-ko/
│               │   └── strings.xml
│               ├── values-pt-rBR/
│               │   └── strings.xml
│               ├── values-ru/
│               │   └── strings.xml
│               ├── values-tr-rTR/
│               │   └── strings.xml
│               ├── values-uk/
│               │   └── strings.xml
│               ├── values-zh/
│               │   └── strings.xml
│               ├── values-zh-rTW/
│               │   └── strings.xml
│               └── xml/
│                   ├── file_paths_private.xml
│                   └── file_paths_public.xml
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.


**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
.DS_Store
/build
/captures

.idea/

# .gitignore template from https://github.com/github/gitignore/blob/master/Android.gitignore
# Built application files
*.apk
*.ap_

# Files for the Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle build files
matisse/build
sample/build



# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/


# Keystore files
*.jks


================================================
FILE: .travis.yml
================================================
language: android

android:
  components:
    - tools
    - platform-tools
    - build-tools-28.0.3
    - android-28
    - extra-android-m2repository

jdk:
  - oraclejdk8

notifications:
  email: false

before_install:
  - chmod +x gradlew
  - mkdir "$ANDROID_HOME/licenses" || true
  - echo -e "\d56f5187479451eabf01fb78af6dfcb131a6481e" "\n24333f8a63b6825ea9c5514f83c2829b004d1fee"> "$ANDROID_HOME/licenses/android-sdk-license"
  - echo -e "\84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"

script:
  - ./gradlew assemble check
  - ./gradlew checkstyle


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at gejiaheng@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: CONTRIBUTING.md
================================================
# Matisse is an Open Source Project

## You Should Know

- To contribute with a small fix, simply create a pull request.
- Better to open an issue to discuss with the team and the community if you're intended to work on something BIG. 
- Check out our [roadmap](https://github.com/zhihu/Matisse/wiki/Roadmap) to see if some features you want is on the way.
- Better to use English to open issues and pull requests.

## Code Style

Please follow [Code Style for Contributors](https://source.android.com/source/code-style) of AOSP except
- Right margin is 120 characters instead of the default 100 value.

And also run `./gradlew checkstyle` to check if there is any style issues before sending a PR.

================================================
FILE: LICENSE.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2017 Zhihu Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
![Image](/image/banner.png)

# Matisse
[![Build Status](https://travis-ci.org/zhihu/Matisse.svg)](https://travis-ci.org/zhihu/Matisse) [ ![Download](https://api.bintray.com/packages/zhihu/maven/matisse/images/download.svg) ](https://bintray.com/zhihu/maven/matisse/_latestVersion)

Matisse is a well-designed local image and video selector for Android. You can  
- Use it in Activity or Fragment
- Select images including JPEG, PNG, GIF and videos including MPEG, MP4 
- Apply different themes, including two built-in themes and custom themes
- Different image loaders
- Define custom filter rules
- More to find out yourself

| Zhihu Style                    | Dracula Style                     | Preview                          |
|:------------------------------:|:---------------------------------:|:--------------------------------:|
|![](image/screenshot_zhihu.png) | ![](image/screenshot_dracula.png) | ![](image/screenshot_preview.png)|

## Download
Gradle:

```groovy
repositories {
    jcenter()
}

dependencies {
    implementation 'com.zhihu.android:matisse:$latest_version'
}
```

Check out [Matisse releases](https://github.com/zhihu/Matisse/releases) to see more unstable versions.

## ProGuard
If you use [Glide](https://github.com/bumptech/glide) as your image engine, add rules as Glide's README says.  
And add extra rule:
```pro
-dontwarn com.squareup.picasso.**
```

If you use [Picasso](https://github.com/square/picasso) as your image engine, add rules as Picasso's README says.  
And add extra rule:
```pro
-dontwarn com.bumptech.glide.**
```
**Attention**: The above progurad rules are correct.

## How do I use Matisse?
#### Permission
The library requires two permissions:
- `android.permission.READ_EXTERNAL_STORAGE`
- `android.permission.WRITE_EXTERNAL_STORAGE`

So if you are targeting Android 6.0+, you need to handle runtime permission request before next step.

#### Simple usage snippet
------
Start `MatisseActivity` from current `Activity` or `Fragment`:

```java
Matisse.from(MainActivity.this)
        .choose(MimeType.allOf())
        .countable(true)
        .maxSelectable(9)
        .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
        .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
        .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
        .thumbnailScale(0.85f)
        .imageEngine(new GlideEngine())
        .showPreview(false) // Default is `true`
        .forResult(REQUEST_CODE_CHOOSE);
```
 
#### Themes
There are two built-in themes you can use to start `MatisseActivity`:
- `R.style.Matisse_Zhihu` (light mode)
- `R.style.Matisse_Dracula` (dark mode)  

And Also you can define your own theme as you wish.

#### Receive Result
In `onActivityResult()` callback of the starting `Activity` or `Fragment`:

```java
List<Uri> mSelected;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_CHOOSE && resultCode == RESULT_OK) {
        mSelected = Matisse.obtainResult(data);
        Log.d("Matisse", "mSelected: " + mSelected);
    }
}
```

#### More
Find more details about Matisse in [wiki](https://github.com/zhihu/Matisse/wiki).

## Contributing
[Matisse is an Open Source Project](https://github.com/zhihu/Matisse/blob/master/CONTRIBUTING.md)

## Thanks
This library is inspired by [Laevatein](https://github.com/nohana/Laevatein) and uses some of its source code.

## License

    Copyright 2017 Zhihu Inc.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.




================================================
FILE: build.gradle
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'
        classpath 'com.novoda:bintray-release:0.9.1'
    }
}

allprojects {
    repositories {
        jcenter()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


================================================
FILE: checkstyle.xml
================================================
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
    "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
    "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
    <!--module name="NewlineAtEndOfFile"/-->
    <module name="FileLength"/>
    <module name="FileTabCharacter"/>

    <!-- Trailing spaces -->
    <module name="RegexpSingleline">
        <property name="format" value="\s+$"/>
        <property name="message" value="Line has trailing spaces."/>
    </module>

    <!-- Space after 'for' and 'if' -->
    <module name="RegexpSingleline">
        <property name="format" value="^\s*(for|if)\b[^ ]"/>
        <property name="message" value="Space needed before opening parenthesis."/>
    </module>

    <!-- For each spacing -->
    <module name="RegexpSingleline">
        <property name="format" value="^\s*for \(.*?([^ ]:|:[^ ])"/>
        <property name="message" value="Space needed around ':' character."/>
    </module>

    <module name="TreeWalker">
        <!--<property name="cacheFile" value="${checkstyle.cache.file}"/>-->

        <!-- Checks for Javadoc comments.                     -->
        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
        <!--module name="JavadocMethod"/-->
        <!--module name="JavadocType"/-->
        <!--module name="JavadocVariable"/-->
        <!--module name="JavadocStyle"/-->


        <!-- Checks for Naming Conventions.                  -->
        <!-- See http://checkstyle.sf.net/config_naming.html -->
        <module name="ConstantName"/>
        <module name="LocalFinalVariableName"/>
        <module name="LocalVariableName">
            <property name="tokens" value="VARIABLE_DEF"/>
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
                     value="Local variable name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="MemberName">
            <property name="format" value="^m[a-zA-Z0-9]*$"/>
            <property name="applyToPublic" value="false"/>
            <message key="name.invalidPattern"
                     value="Member ''{0}'' must start with a lowercase ''m'' (checked pattern ''{1}'')."
            />
        </module>
        <module name="MethodName">
            <property name="format" value="^[a-z][a-zA-Z0-9_]*$"/>
        </module>
        <module name="PackageName"/>
        <module name="ParameterName"/>
        <module name="StaticVariableName">
            <property name="format" value="^s[a-zA-Z0-9]*$"/>
            <message key="name.invalidPattern"
                     value="Member ''{0}'' must start with a lowercase ''s'' (checked pattern ''{1}'')."
            />
        </module>
        <module name="TypeName">
            <property name="format" value="^[A-Z][a-zA-Z0-9_]*$"/>
        </module>


        <!-- Checks for imports                              -->
        <!-- See http://checkstyle.sf.net/config_import.html -->
        <module name="AvoidStarImport"/>
        <module name="IllegalImport"/>
        <module name="RedundantImport"/>
        <module name="UnusedImports">
            <property name="processJavadoc" value="true"/>
        </module>


        <!-- Checks for Size Violations.                    -->
        <!-- See http://checkstyle.sf.net/config_sizes.html -->
        <module name="LineLength">
            <property name="severity" value="warning"/>
            <property name="max" value="120"/>
            <property name="ignorePattern"
                      value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
        </module>
        <!--<module name="MethodLength"/>-->
        <!--<module name="ParameterNumber"/>-->


        <!-- Checks for whitespace                               -->
        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
        <module name="GenericWhitespace"/>
        <module name="EmptyForIteratorPad"/>
        <module name="MethodParamPad"/>
        <module name="NoWhitespaceAfter"/>
        <module name="NoWhitespaceBefore"/>
        <module name="OperatorWrap"/>
        <module name="ParenPad"/>
        <module name="TypecastParenPad"/>
        <module name="WhitespaceAfter"/>
        <module name="WhitespaceAround"/>


        <!-- Modifier Checks                                    -->
        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
        <!--module name="ModifierOrder"/-->
        <module name="RedundantModifier"/>


        <!-- Checks for blocks. You know, those {}'s         -->
        <!-- See http://checkstyle.sf.net/config_blocks.html -->
        <module name="AvoidNestedBlocks"/>
        <!--<module name="EmptyBlock"/>-->
        <module name="LeftCurly"/>
        <module name="NeedBraces">
            <property name="tokens" value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE"/>
        </module>
        <module name="RightCurly"/>


        <!-- Checks for common coding problems               -->
        <!-- See http://checkstyle.sf.net/config_coding.html -->
        <!--<module name="AvoidInlineConditionals"/>-->
        <module name="CovariantEquals"/>
        <!--<module name="DoubleCheckedLocking"/>-->
        <module name="EmptyStatement"/>
        <module name="EqualsAvoidNull"/>
        <module name="EqualsHashCode"/>
        <!--<module name="HiddenField"/>-->
        <module name="IllegalInstantiation"/>
        <module name="InnerAssignment"/>
        <!--<module name="MagicNumber"/>-->
        <module name="MissingSwitchDefault"/>
        <!--<module name="RedundantThrows"/>-->
        <module name="SimplifyBooleanExpression"/>
        <module name="SimplifyBooleanReturn"/>

        <!-- Checks for class design                         -->
        <!-- See http://checkstyle.sf.net/config_design.html -->
        <!--module name="DesignForExtension"/-->
        <!--module name="FinalClass"/-->
        <!--<module name="HideUtilityClassConstructor"/>-->
        <module name="InterfaceIsType"/>
        <!--<module name="VisibilityModifier"/>-->


        <!-- Miscellaneous other checks.                   -->
        <!-- See http://checkstyle.sf.net/config_misc.html -->
        <module name="ArrayTypeStyle"/>
        <!--module name="FinalParameters"/-->
        <module name="TodoComment"/>
        <module name="UpperEll"/>
    </module>
</module>

================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Thu Aug 22 11:37:43 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true


================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: matisse/build.gradle
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
apply plugin: 'com.android.library'
apply plugin: 'com.novoda.bintray-release'
apply plugin: 'checkstyle'

android {
    compileSdkVersion 29
    buildToolsVersion '29.0.2'

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 29
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    lintOptions {
        abortOnError false
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation "androidx.appcompat:appcompat:1.1.0"
    implementation "androidx.annotation:annotation:1.1.0"
    implementation "androidx.recyclerview:recyclerview:1.0.0"
    implementation 'it.sephiroth.android.library.imagezoom:library:1.0.4'

    compileOnly 'com.github.bumptech.glide:glide:4.9.0'
    compileOnly 'com.squareup.picasso:picasso:2.5.2'
}

// jcenter configuration for novoda's bintray-release
// $ ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=false
publish {
    userOrg = 'zhihu'
    groupId = 'com.zhihu.android'
    artifactId = 'matisse'
    publishVersion = '0.5.3-beta3'
    desc = 'A well-designed local image selector for Android'
    website = 'https://www.zhihu.com/'
}

task javadoc(type: Javadoc) {
    options.encoding = "utf-8"
}

checkstyle {
    toolVersion = '7.6.1'
}

tasks.withType(Javadoc) {
    options.addStringOption('Xdoclint:none', '-quiet')
    options.addStringOption('encoding', 'UTF-8')
}

task checkstyle(type:Checkstyle) {
    description 'Runs Checkstyle inspection against matisse sourcesets.'
    group = 'Code Quality'
    configFile rootProject.file('checkstyle.xml')
    ignoreFailures = false
    showViolations true
    classpath = files()
    source 'src/main/java'
}


================================================
FILE: matisse/gradle.properties
================================================


================================================
FILE: matisse/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Library/android-sdk-macosx/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

-dontwarn com.squareup.okhttp.**


================================================
FILE: matisse/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  Copyright 2017 Zhihu Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  -->
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zhihu.matisse">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application>
        <activity android:name="com.zhihu.matisse.ui.MatisseActivity"/>
        <activity android:name="com.zhihu.matisse.internal.ui.AlbumPreviewActivity"/>
        <activity android:name="com.zhihu.matisse.internal.ui.SelectedPreviewActivity"/>
    </application>
</manifest>

================================================
FILE: matisse/src/main/java/com/zhihu/matisse/Matisse.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.zhihu.matisse.ui.MatisseActivity;

import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Set;

/**
 * Entry for Matisse's media selection.
 */
public final class Matisse {

    private final WeakReference<Activity> mContext;
    private final WeakReference<Fragment> mFragment;

    private Matisse(Activity activity) {
        this(activity, null);
    }

    private Matisse(Fragment fragment) {
        this(fragment.getActivity(), fragment);
    }

    private Matisse(Activity activity, Fragment fragment) {
        mContext = new WeakReference<>(activity);
        mFragment = new WeakReference<>(fragment);
    }

    /**
     * Start Matisse from an Activity.
     * <p>
     * This Activity's {@link Activity#onActivityResult(int, int, Intent)} will be called when user
     * finishes selecting.
     *
     * @param activity Activity instance.
     * @return Matisse instance.
     */
    public static Matisse from(Activity activity) {
        return new Matisse(activity);
    }

    /**
     * Start Matisse from a Fragment.
     * <p>
     * This Fragment's {@link Fragment#onActivityResult(int, int, Intent)} will be called when user
     * finishes selecting.
     *
     * @param fragment Fragment instance.
     * @return Matisse instance.
     */
    public static Matisse from(Fragment fragment) {
        return new Matisse(fragment);
    }

    /**
     * Obtain user selected media' {@link Uri} list in the starting Activity or Fragment.
     *
     * @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
     *             {@link Fragment#onActivityResult(int, int, Intent)}.
     * @return User selected media' {@link Uri} list.
     */
    public static List<Uri> obtainResult(Intent data) {
        return data.getParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION);
    }

    /**
     * Obtain user selected media path list in the starting Activity or Fragment.
     *
     * @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
     *             {@link Fragment#onActivityResult(int, int, Intent)}.
     * @return User selected media path list.
     */
    public static List<String> obtainPathResult(Intent data) {
        return data.getStringArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION_PATH);
    }

    /**
     * Obtain state whether user decide to use selected media in original
     *
     * @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
     *             {@link Fragment#onActivityResult(int, int, Intent)}.
     * @return Whether use original photo
     */
    public static boolean obtainOriginalState(Intent data) {
        return data.getBooleanExtra(MatisseActivity.EXTRA_RESULT_ORIGINAL_ENABLE, false);
    }

    /**
     * MIME types the selection constrains on.
     * <p>
     * Types not included in the set will still be shown in the grid but can't be chosen.
     *
     * @param mimeTypes MIME types set user can choose from.
     * @return {@link SelectionCreator} to build select specifications.
     * @see MimeType
     * @see SelectionCreator
     */
    public SelectionCreator choose(Set<MimeType> mimeTypes) {
        return this.choose(mimeTypes, true);
    }

    /**
     * MIME types the selection constrains on.
     * <p>
     * Types not included in the set will still be shown in the grid but can't be chosen.
     *
     * @param mimeTypes          MIME types set user can choose from.
     * @param mediaTypeExclusive Whether can choose images and videos at the same time during one single choosing
     *                           process. true corresponds to not being able to choose images and videos at the same
     *                           time, and false corresponds to being able to do this.
     * @return {@link SelectionCreator} to build select specifications.
     * @see MimeType
     * @see SelectionCreator
     */
    public SelectionCreator choose(Set<MimeType> mimeTypes, boolean mediaTypeExclusive) {
        return new SelectionCreator(this, mimeTypes, mediaTypeExclusive);
    }

    @Nullable
    Activity getActivity() {
        return mContext.get();
    }

    @Nullable
    Fragment getFragment() {
        return mFragment != null ? mFragment.get() : null;
    }

}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/MimeType.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse;

import android.content.ContentResolver;
import android.net.Uri;
import android.text.TextUtils;

import androidx.collection.ArraySet;

import android.webkit.MimeTypeMap;

import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;

/**
 * MIME Type enumeration to restrict selectable media on the selection activity. Matisse only supports images and
 * videos.
 * <p>
 * Good example of mime types Android supports:
 * https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/MediaFile.java
 */
@SuppressWarnings("unused")
public enum MimeType {

    // ============== images ==============
    JPEG("image/jpeg", arraySetOf(
            "jpg",
            "jpeg"
    )),
    PNG("image/png", arraySetOf(
            "png"
    )),
    GIF("image/gif", arraySetOf(
            "gif"
    )),
    BMP("image/x-ms-bmp", arraySetOf(
            "bmp"
    )),
    WEBP("image/webp", arraySetOf(
            "webp"
    )),

    // ============== videos ==============
    MPEG("video/mpeg", arraySetOf(
            "mpeg",
            "mpg"
    )),
    MP4("video/mp4", arraySetOf(
            "mp4",
            "m4v"
    )),
    QUICKTIME("video/quicktime", arraySetOf(
            "mov"
    )),
    THREEGPP("video/3gpp", arraySetOf(
            "3gp",
            "3gpp"
    )),
    THREEGPP2("video/3gpp2", arraySetOf(
            "3g2",
            "3gpp2"
    )),
    MKV("video/x-matroska", arraySetOf(
            "mkv"
    )),
    WEBM("video/webm", arraySetOf(
            "webm"
    )),
    TS("video/mp2ts", arraySetOf(
            "ts"
    )),
    AVI("video/avi", arraySetOf(
            "avi"
    ));

    private final String mMimeTypeName;
    private final Set<String> mExtensions;

    MimeType(String mimeTypeName, Set<String> extensions) {
        mMimeTypeName = mimeTypeName;
        mExtensions = extensions;
    }

    public static Set<MimeType> ofAll() {
        return EnumSet.allOf(MimeType.class);
    }

    public static Set<MimeType> of(MimeType type, MimeType... rest) {
        return EnumSet.of(type, rest);
    }

    public static Set<MimeType> ofImage() {
        return EnumSet.of(JPEG, PNG, GIF, BMP, WEBP);
    }

    public static Set<MimeType> ofImage(boolean onlyGif) {
        return EnumSet.of(GIF);
    }

    public static Set<MimeType> ofGif() {
        return ofImage(true);
    }

    public static Set<MimeType> ofVideo() {
        return EnumSet.of(MPEG, MP4, QUICKTIME, THREEGPP, THREEGPP2, MKV, WEBM, TS, AVI);
    }

    public static boolean isImage(String mimeType) {
        if (mimeType == null) return false;
        return mimeType.startsWith("image");
    }

    public static boolean isVideo(String mimeType) {
        if (mimeType == null) return false;
        return mimeType.startsWith("video");
    }

    public static boolean isGif(String mimeType) {
        if (mimeType == null) return false;
        return mimeType.equals(MimeType.GIF.toString());
    }

    private static Set<String> arraySetOf(String... suffixes) {
        return new ArraySet<>(Arrays.asList(suffixes));
    }

    @Override
    public String toString() {
        return mMimeTypeName;
    }

    public boolean checkType(ContentResolver resolver, Uri uri) {
        MimeTypeMap map = MimeTypeMap.getSingleton();
        if (uri == null) {
            return false;
        }
        String type = map.getExtensionFromMimeType(resolver.getType(uri));
        String path = null;
        // lazy load the path and prevent resolve for multiple times
        boolean pathParsed = false;
        for (String extension : mExtensions) {
            if (extension.equals(type)) {
                return true;
            }
            if (!pathParsed) {
                // we only resolve the path for one time
                path = PhotoMetadataUtils.getPath(resolver, uri);
                if (!TextUtils.isEmpty(path)) {
                    path = path.toLowerCase(Locale.US);
                }
                pathParsed = true;
            }
            if (path != null && path.endsWith(extension)) {
                return true;
            }
        }
        return false;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse;

import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;

import com.zhihu.matisse.engine.ImageEngine;
import com.zhihu.matisse.filter.Filter;
import com.zhihu.matisse.internal.entity.CaptureStrategy;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.listener.OnCheckedListener;
import com.zhihu.matisse.listener.OnSelectedListener;
import com.zhihu.matisse.ui.MatisseActivity;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Set;

import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;

/**
 * Fluent API for building media select specification.
 */
@SuppressWarnings("unused")
public final class SelectionCreator {
    private final Matisse mMatisse;
    private final SelectionSpec mSelectionSpec;

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    @IntDef({
            SCREEN_ORIENTATION_UNSPECIFIED,
            SCREEN_ORIENTATION_LANDSCAPE,
            SCREEN_ORIENTATION_PORTRAIT,
            SCREEN_ORIENTATION_USER,
            SCREEN_ORIENTATION_BEHIND,
            SCREEN_ORIENTATION_SENSOR,
            SCREEN_ORIENTATION_NOSENSOR,
            SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
            SCREEN_ORIENTATION_SENSOR_PORTRAIT,
            SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
            SCREEN_ORIENTATION_REVERSE_PORTRAIT,
            SCREEN_ORIENTATION_FULL_SENSOR,
            SCREEN_ORIENTATION_USER_LANDSCAPE,
            SCREEN_ORIENTATION_USER_PORTRAIT,
            SCREEN_ORIENTATION_FULL_USER,
            SCREEN_ORIENTATION_LOCKED
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface ScreenOrientation {
    }

    /**
     * Constructs a new specification builder on the context.
     *
     * @param matisse   a requester context wrapper.
     * @param mimeTypes MIME type set to select.
     */
    SelectionCreator(Matisse matisse, @NonNull Set<MimeType> mimeTypes, boolean mediaTypeExclusive) {
        mMatisse = matisse;
        mSelectionSpec = SelectionSpec.getCleanInstance();
        mSelectionSpec.mimeTypeSet = mimeTypes;
        mSelectionSpec.mediaTypeExclusive = mediaTypeExclusive;
        mSelectionSpec.orientation = SCREEN_ORIENTATION_UNSPECIFIED;
    }

    /**
     * Whether to show only one media type if choosing medias are only images or videos.
     *
     * @param showSingleMediaType whether to show only one media type, either images or videos.
     * @return {@link SelectionCreator} for fluent API.
     * @see SelectionSpec#onlyShowImages()
     * @see SelectionSpec#onlyShowVideos()
     */
    public SelectionCreator showSingleMediaType(boolean showSingleMediaType) {
        mSelectionSpec.showSingleMediaType = showSingleMediaType;
        return this;
    }

    /**
     * Theme for media selecting Activity.
     * <p>
     * There are two built-in themes:
     * 1. com.zhihu.matisse.R.style.Matisse_Zhihu;
     * 2. com.zhihu.matisse.R.style.Matisse_Dracula
     * you can define a custom theme derived from the above ones or other themes.
     *
     * @param themeId theme resource id. Default value is com.zhihu.matisse.R.style.Matisse_Zhihu.
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator theme(@StyleRes int themeId) {
        mSelectionSpec.themeId = themeId;
        return this;
    }

    /**
     * Show a auto-increased number or a check mark when user select media.
     *
     * @param countable true for a auto-increased number from 1, false for a check mark. Default
     *                  value is false.
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator countable(boolean countable) {
        mSelectionSpec.countable = countable;
        return this;
    }

    /**
     * Maximum selectable count.
     *
     * @param maxSelectable Maximum selectable count. Default value is 1.
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator maxSelectable(int maxSelectable) {
        if (maxSelectable < 1)
            throw new IllegalArgumentException("maxSelectable must be greater than or equal to one");
        if (mSelectionSpec.maxImageSelectable > 0 || mSelectionSpec.maxVideoSelectable > 0)
            throw new IllegalStateException("already set maxImageSelectable and maxVideoSelectable");
        mSelectionSpec.maxSelectable = maxSelectable;
        return this;
    }

    /**
     * Only useful when {@link SelectionSpec#mediaTypeExclusive} set true and you want to set different maximum
     * selectable files for image and video media types.
     *
     * @param maxImageSelectable Maximum selectable count for image.
     * @param maxVideoSelectable Maximum selectable count for video.
     * @return  {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator maxSelectablePerMediaType(int maxImageSelectable, int maxVideoSelectable) {
        if (maxImageSelectable < 1 || maxVideoSelectable < 1)
            throw new IllegalArgumentException(("max selectable must be greater than or equal to one"));
        mSelectionSpec.maxSelectable = -1;
        mSelectionSpec.maxImageSelectable = maxImageSelectable;
        mSelectionSpec.maxVideoSelectable = maxVideoSelectable;
        return this;
    }

    /**
     * Add filter to filter each selecting item.
     *
     * @param filter {@link Filter}
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator addFilter(@NonNull Filter filter) {
        if (mSelectionSpec.filters == null) {
            mSelectionSpec.filters = new ArrayList<>();
        }
        if (filter == null) throw new IllegalArgumentException("filter cannot be null");
        mSelectionSpec.filters.add(filter);
        return this;
    }

    /**
     * Determines whether the photo capturing is enabled or not on the media grid view.
     * <p>
     * If this value is set true, photo capturing entry will appear only on All Media's page.
     *
     * @param enable Whether to enable capturing or not. Default value is false;
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator capture(boolean enable) {
        mSelectionSpec.capture = enable;
        return this;
    }

    /**
     * Show a original photo check options.Let users decide whether use original photo after select
     *
     * @param enable Whether to enable original photo or not
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator originalEnable(boolean enable) {
        mSelectionSpec.originalable = enable;
        return this;
    }


    /**
     * Determines Whether to hide top and bottom toolbar in PreView mode ,when user tap the picture
     * @param enable
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator autoHideToolbarOnSingleTap(boolean enable) {
        mSelectionSpec.autoHideToobar = enable;
        return this;
    }

    /**
     * Maximum original size,the unit is MB. Only useful when {link@originalEnable} set true
     *
     * @param size Maximum original size. Default value is Integer.MAX_VALUE
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator maxOriginalSize(int size) {
        mSelectionSpec.originalMaxSize = size;
        return this;
    }

    /**
     * Capture strategy provided for the location to save photos including internal and external
     * storage and also a authority for {@link androidx.core.content.FileProvider}.
     *
     * @param captureStrategy {@link CaptureStrategy}, needed only when capturing is enabled.
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator captureStrategy(CaptureStrategy captureStrategy) {
        mSelectionSpec.captureStrategy = captureStrategy;
        return this;
    }

    /**
     * Set the desired orientation of this activity.
     *
     * @param orientation An orientation constant as used in {@link ScreenOrientation}.
     *                    Default value is {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT}.
     * @return {@link SelectionCreator} for fluent API.
     * @see Activity#setRequestedOrientation(int)
     */
    public SelectionCreator restrictOrientation(@ScreenOrientation int orientation) {
        mSelectionSpec.orientation = orientation;
        return this;
    }

    /**
     * Set a fixed span count for the media grid. Same for different screen orientations.
     * <p>
     * This will be ignored when {@link #gridExpectedSize(int)} is set.
     *
     * @param spanCount Requested span count.
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator spanCount(int spanCount) {
        if (spanCount < 1) throw new IllegalArgumentException("spanCount cannot be less than 1");
        mSelectionSpec.spanCount = spanCount;
        return this;
    }

    /**
     * Set expected size for media grid to adapt to different screen sizes. This won't necessarily
     * be applied cause the media grid should fill the view container. The measured media grid's
     * size will be as close to this value as possible.
     *
     * @param size Expected media grid size in pixel.
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator gridExpectedSize(int size) {
        mSelectionSpec.gridExpectedSize = size;
        return this;
    }

    /**
     * Photo thumbnail's scale compared to the View's size. It should be a float value in (0.0,
     * 1.0].
     *
     * @param scale Thumbnail's scale in (0.0, 1.0]. Default value is 0.5.
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator thumbnailScale(float scale) {
        if (scale <= 0f || scale > 1f)
            throw new IllegalArgumentException("Thumbnail scale must be between (0.0, 1.0]");
        mSelectionSpec.thumbnailScale = scale;
        return this;
    }

    /**
     * Provide an image engine.
     * <p>
     * There are two built-in image engines:
     * 1. {@link com.zhihu.matisse.engine.impl.GlideEngine}
     * 2. {@link com.zhihu.matisse.engine.impl.PicassoEngine}
     * And you can implement your own image engine.
     *
     * @param imageEngine {@link ImageEngine}
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator imageEngine(ImageEngine imageEngine) {
        mSelectionSpec.imageEngine = imageEngine;
        return this;
    }

    /**
     * Set listener for callback immediately when user select or unselect something.
     * <p>
     * It's a redundant API with {@link Matisse#obtainResult(Intent)},
     * we only suggest you to use this API when you need to do something immediately.
     *
     * @param listener {@link OnSelectedListener}
     * @return {@link SelectionCreator} for fluent API.
     */
    @NonNull
    public SelectionCreator setOnSelectedListener(@Nullable OnSelectedListener listener) {
        mSelectionSpec.onSelectedListener = listener;
        return this;
    }

    /**
     * Set listener for callback immediately when user check or uncheck original.
     *
     * @param listener {@link OnSelectedListener}
     * @return {@link SelectionCreator} for fluent API.
     */
    public SelectionCreator setOnCheckedListener(@Nullable OnCheckedListener listener) {
        mSelectionSpec.onCheckedListener = listener;
        return this;
    }

    /**
     * Start to select media and wait for result.
     *
     * @param requestCode Identity of the request Activity or Fragment.
     */
    public void forResult(int requestCode) {
        Activity activity = mMatisse.getActivity();
        if (activity == null) {
            return;
        }

        Intent intent = new Intent(activity, MatisseActivity.class);

        Fragment fragment = mMatisse.getFragment();
        if (fragment != null) {
            fragment.startActivityForResult(intent, requestCode);
        } else {
            activity.startActivityForResult(intent, requestCode);
        }
    }

    public SelectionCreator showPreview(boolean showPreview) {
        mSelectionSpec.showPreview = showPreview;
        return this;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/engine/ImageEngine.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.engine;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;

/**
 * Image loader interface. There are predefined {@link com.zhihu.matisse.engine.impl.GlideEngine}
 * and {@link com.zhihu.matisse.engine.impl.PicassoEngine}.
 */
@SuppressWarnings("unused")
public interface ImageEngine {

    /**
     * Load thumbnail of a static image resource.
     *
     * @param context     Context
     * @param resize      Desired size of the origin image
     * @param placeholder Placeholder drawable when image is not loaded yet
     * @param imageView   ImageView widget
     * @param uri         Uri of the loaded image
     */
    void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri);

    /**
     * Load thumbnail of a gif image resource. You don't have to load an animated gif when it's only
     * a thumbnail tile.
     *
     * @param context     Context
     * @param resize      Desired size of the origin image
     * @param placeholder Placeholder drawable when image is not loaded yet
     * @param imageView   ImageView widget
     * @param uri         Uri of the loaded image
     */
    void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri);

    /**
     * Load a static image resource.
     *
     * @param context   Context
     * @param resizeX   Desired x-size of the origin image
     * @param resizeY   Desired y-size of the origin image
     * @param imageView ImageView widget
     * @param uri       Uri of the loaded image
     */
    void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri);

    /**
     * Load a gif image resource.
     *
     * @param context   Context
     * @param resizeX   Desired x-size of the origin image
     * @param resizeY   Desired y-size of the origin image
     * @param imageView ImageView widget
     * @param uri       Uri of the loaded image
     */
    void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri);

    /**
     * Whether this implementation supports animated gif.
     * Just knowledge of it, convenient for users.
     *
     * @return true support animated gif, false do not support animated gif.
     */
    boolean supportAnimatedGif();
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/engine/impl/GlideEngine.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.engine.impl;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.request.RequestOptions;
import com.zhihu.matisse.engine.ImageEngine;

/**
 * {@link ImageEngine} implementation using Glide.
 */

public class GlideEngine implements ImageEngine {

    @Override
    public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
        Glide.with(context)
                .asBitmap() // some .jpeg files are actually gif
                .load(uri)
                .apply(new RequestOptions()
                        .override(resize, resize)
                        .placeholder(placeholder)
                        .centerCrop())
                .into(imageView);
    }

    @Override
    public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView,
                                 Uri uri) {
        Glide.with(context)
                .asBitmap() // some .jpeg files are actually gif
                .load(uri)
                .apply(new RequestOptions()
                        .override(resize, resize)
                        .placeholder(placeholder)
                        .centerCrop())
                .into(imageView);
    }

    @Override
    public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
        Glide.with(context)
                .load(uri)
                .apply(new RequestOptions()
                        .override(resizeX, resizeY)
                        .priority(Priority.HIGH)
                        .fitCenter())
                .into(imageView);
    }

    @Override
    public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
        Glide.with(context)
                .asGif()
                .load(uri)
                .apply(new RequestOptions()
                        .override(resizeX, resizeY)
                        .priority(Priority.HIGH)
                        .fitCenter())
                .into(imageView);
    }

    @Override
    public boolean supportAnimatedGif() {
        return true;
    }

}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/engine/impl/PicassoEngine.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.engine.impl;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;

import com.squareup.picasso.Picasso;
import com.zhihu.matisse.engine.ImageEngine;

/**
 * {@link ImageEngine} implementation using Picasso.
 */

public class PicassoEngine implements ImageEngine {

    @Override
    public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
        Picasso.with(context).load(uri).placeholder(placeholder)
                .resize(resize, resize)
                .centerCrop()
                .into(imageView);
    }

    @Override
    public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView,
                                 Uri uri) {
        loadThumbnail(context, resize, placeholder, imageView, uri);
    }

    @Override
    public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
        Picasso.with(context).load(uri).resize(resizeX, resizeY).priority(Picasso.Priority.HIGH)
                .centerInside().into(imageView);
    }

    @Override
    public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
        loadImage(context, resizeX, resizeY, imageView, uri);
    }

    @Override
    public boolean supportAnimatedGif() {
        return false;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/filter/Filter.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.filter;

import android.content.Context;

import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.SelectionCreator;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.IncapableCause;

import java.util.Set;

/**
 * Filter for choosing a {@link Item}. You can add multiple Filters through
 * {@link SelectionCreator#addFilter(Filter)}.
 */
@SuppressWarnings("unused")
public abstract class Filter {
    /**
     * Convenient constant for a minimum value.
     */
    public static final int MIN = 0;
    /**
     * Convenient constant for a maximum value.
     */
    public static final int MAX = Integer.MAX_VALUE;
    /**
     * Convenient constant for 1024.
     */
    public static final int K = 1024;

    /**
     * Against what mime types this filter applies.
     */
    protected abstract Set<MimeType> constraintTypes();

    /**
     * Invoked for filtering each item.
     *
     * @return null if selectable, {@link IncapableCause} if not selectable.
     */
    public abstract IncapableCause filter(Context context, Item item);

    /**
     * Whether an {@link Item} need filtering.
     */
    protected boolean needFiltering(Context context, Item item) {
        for (MimeType type : constraintTypes()) {
            if (type.checkType(context.getContentResolver(), item.getContentUri())) {
                return true;
            }
        }
        return false;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.entity;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.Nullable;

import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.loader.AlbumLoader;

public class Album implements Parcelable {
    public static final Creator<Album> CREATOR = new Creator<Album>() {
        @Nullable
        @Override
        public Album createFromParcel(Parcel source) {
            return new Album(source);
        }

        @Override
        public Album[] newArray(int size) {
            return new Album[size];
        }
    };
    public static final String ALBUM_ID_ALL = String.valueOf(-1);
    public static final String ALBUM_NAME_ALL = "All";

    private final String mId;
    private final Uri mCoverUri;
    private final String mDisplayName;
    private long mCount;

    public Album(String id, Uri coverUri, String albumName, long count) {
        mId = id;
        mCoverUri = coverUri;
        mDisplayName = albumName;
        mCount = count;
    }

    private Album(Parcel source) {
        mId = source.readString();
        mCoverUri = source.readParcelable(Uri.class.getClassLoader());
        mDisplayName = source.readString();
        mCount = source.readLong();
    }

    /**
     * Constructs a new {@link Album} entity from the {@link Cursor}.
     * This method is not responsible for managing cursor resource, such as close, iterate, and so on.
     */
    public static Album valueOf(Cursor cursor) {
        String clumn = cursor.getString(cursor.getColumnIndex(AlbumLoader.COLUMN_URI));
        return new Album(
                cursor.getString(cursor.getColumnIndex("bucket_id")),
                Uri.parse(clumn != null ? clumn : ""),
                cursor.getString(cursor.getColumnIndex("bucket_display_name")),
                cursor.getLong(cursor.getColumnIndex(AlbumLoader.COLUMN_COUNT)));
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mId);
        dest.writeParcelable(mCoverUri, 0);
        dest.writeString(mDisplayName);
        dest.writeLong(mCount);
    }

    public String getId() {
        return mId;
    }

    public Uri getCoverUri() {
        return mCoverUri;
    }

    public long getCount() {
        return mCount;
    }

    public void addCaptureCount() {
        mCount++;
    }

    public String getDisplayName(Context context) {
        if (isAll()) {
            return context.getString(R.string.album_name_all);
        }
        return mDisplayName;
    }

    public boolean isAll() {
        return ALBUM_ID_ALL.equals(mId);
    }

    public boolean isEmpty() {
        return mCount == 0;
    }

}

================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/CaptureStrategy.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.entity;

public class CaptureStrategy {

    public final boolean isPublic;
    public final String authority;
    public final String directory;

    public CaptureStrategy(boolean isPublic, String authority) {
        this(isPublic, authority, null);
    }

    public CaptureStrategy(boolean isPublic, String authority, String directory) {
        this.isPublic = isPublic;
        this.authority = authority;
        this.directory = directory;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.entity;

import android.content.Context;
import androidx.annotation.IntDef;
import androidx.fragment.app.FragmentActivity;
import android.widget.Toast;

import com.zhihu.matisse.internal.ui.widget.IncapableDialog;

import java.lang.annotation.Retention;

import static java.lang.annotation.RetentionPolicy.SOURCE;

@SuppressWarnings("unused")
public class IncapableCause {
    public static final int TOAST = 0x00;
    public static final int DIALOG = 0x01;
    public static final int NONE = 0x02;

    @Retention(SOURCE)
    @IntDef({TOAST, DIALOG, NONE})
    public @interface Form {
    }

    private int mForm = TOAST;
    private String mTitle;
    private String mMessage;

    public IncapableCause(String message) {
        mMessage = message;
    }

    public IncapableCause(String title, String message) {
        mTitle = title;
        mMessage = message;
    }

    public IncapableCause(@Form int form, String message) {
        mForm = form;
        mMessage = message;
    }

    public IncapableCause(@Form int form, String title, String message) {
        mForm = form;
        mTitle = title;
        mMessage = message;
    }

    public static void handleCause(Context context, IncapableCause cause) {
        if (cause == null)
            return;

        switch (cause.mForm) {
            case NONE:
                // do nothing.
                break;
            case DIALOG:
                IncapableDialog incapableDialog = IncapableDialog.newInstance(cause.mTitle, cause.mMessage);
                incapableDialog.show(((FragmentActivity) context).getSupportFragmentManager(),
                        IncapableDialog.class.getName());
                break;
            case TOAST:
            default:
                Toast.makeText(context, cause.mMessage, Toast.LENGTH_SHORT).show();
                break;
        }
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.entity;

import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;
import androidx.annotation.Nullable;

import com.zhihu.matisse.MimeType;

public class Item implements Parcelable {
    public static final Creator<Item> CREATOR = new Creator<Item>() {
        @Override
        @Nullable
        public Item createFromParcel(Parcel source) {
            return new Item(source);
        }

        @Override
        public Item[] newArray(int size) {
            return new Item[size];
        }
    };
    public static final long ITEM_ID_CAPTURE = -1;
    public static final String ITEM_DISPLAY_NAME_CAPTURE = "Capture";
    public final long id;
    public final String mimeType;
    public final Uri uri;
    public final long size;
    public final long duration; // only for video, in ms

    private Item(long id, String mimeType, long size, long duration) {
        this.id = id;
        this.mimeType = mimeType;
        Uri contentUri;
        if (isImage()) {
            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        } else if (isVideo()) {
            contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        } else {
            // ?
            contentUri = MediaStore.Files.getContentUri("external");
        }
        this.uri = ContentUris.withAppendedId(contentUri, id);
        this.size = size;
        this.duration = duration;
    }

    private Item(Parcel source) {
        id = source.readLong();
        mimeType = source.readString();
        uri = source.readParcelable(Uri.class.getClassLoader());
        size = source.readLong();
        duration = source.readLong();
    }

    public static Item valueOf(Cursor cursor) {
        return new Item(cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID)),
                cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)),
                cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)),
                cursor.getLong(cursor.getColumnIndex("duration")));
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(id);
        dest.writeString(mimeType);
        dest.writeParcelable(uri, 0);
        dest.writeLong(size);
        dest.writeLong(duration);
    }

    public Uri getContentUri() {
        return uri;
    }

    public boolean isCapture() {
        return id == ITEM_ID_CAPTURE;
    }

    public boolean isImage() {
        return MimeType.isImage(mimeType);
    }

    public boolean isGif() {
        return MimeType.isGif(mimeType);
    }

    public boolean isVideo() {
        return MimeType.isVideo(mimeType);
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Item)) {
            return false;
        }

        Item other = (Item) obj;
        return id == other.id
                && (mimeType != null && mimeType.equals(other.mimeType)
                    || (mimeType == null && other.mimeType == null))
                && (uri != null && uri.equals(other.uri)
                    || (uri == null && other.uri == null))
                && size == other.size
                && duration == other.duration;
    }

    @Override
    public int hashCode() {
        int result = 1;
        result = 31 * result + Long.valueOf(id).hashCode();
        if (mimeType != null) {
            result = 31 * result + mimeType.hashCode();
        }
        result = 31 * result + uri.hashCode();
        result = 31 * result + Long.valueOf(size).hashCode();
        result = 31 * result + Long.valueOf(duration).hashCode();
        return result;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.entity;

import android.content.pm.ActivityInfo;

import androidx.annotation.StyleRes;

import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.R;
import com.zhihu.matisse.engine.ImageEngine;
import com.zhihu.matisse.engine.impl.GlideEngine;
import com.zhihu.matisse.filter.Filter;
import com.zhihu.matisse.listener.OnCheckedListener;
import com.zhihu.matisse.listener.OnSelectedListener;

import java.util.List;
import java.util.Set;

public final class SelectionSpec {

    public Set<MimeType> mimeTypeSet;
    public boolean mediaTypeExclusive;
    public boolean showSingleMediaType;
    @StyleRes
    public int themeId;
    public int orientation;
    public boolean countable;
    public int maxSelectable;
    public int maxImageSelectable;
    public int maxVideoSelectable;
    public List<Filter> filters;
    public boolean capture;
    public CaptureStrategy captureStrategy;
    public int spanCount;
    public int gridExpectedSize;
    public float thumbnailScale;
    public ImageEngine imageEngine;
    public boolean hasInited;
    public OnSelectedListener onSelectedListener;
    public boolean originalable;
    public boolean autoHideToobar;
    public int originalMaxSize;
    public OnCheckedListener onCheckedListener;
    public boolean showPreview;

    private SelectionSpec() {
    }

    public static SelectionSpec getInstance() {
        return InstanceHolder.INSTANCE;
    }

    public static SelectionSpec getCleanInstance() {
        SelectionSpec selectionSpec = getInstance();
        selectionSpec.reset();
        return selectionSpec;
    }

    private void reset() {
        mimeTypeSet = null;
        mediaTypeExclusive = true;
        showSingleMediaType = false;
        themeId = R.style.Matisse_Zhihu;
        orientation = 0;
        countable = false;
        maxSelectable = 1;
        maxImageSelectable = 0;
        maxVideoSelectable = 0;
        filters = null;
        capture = false;
        captureStrategy = null;
        spanCount = 3;
        gridExpectedSize = 0;
        thumbnailScale = 0.5f;
        imageEngine = new GlideEngine();
        hasInited = true;
        originalable = false;
        autoHideToobar = false;
        originalMaxSize = Integer.MAX_VALUE;
        showPreview = true;
    }

    public boolean singleSelectionModeEnabled() {
        return !countable && (maxSelectable == 1 || (maxImageSelectable == 1 && maxVideoSelectable == 1));
    }

    public boolean needOrientationRestriction() {
        return orientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
    }

    public boolean onlyShowImages() {
        return showSingleMediaType && MimeType.ofImage().containsAll(mimeTypeSet);
    }

    public boolean onlyShowVideos() {
        return showSingleMediaType && MimeType.ofVideo().containsAll(mimeTypeSet);
    }

    public boolean onlyShowGif() {
        return showSingleMediaType && MimeType.ofGif().equals(mimeTypeSet);
    }

    private static final class InstanceHolder {
        private static final SelectionSpec INSTANCE = new SelectionSpec();
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.loader;

import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;

import androidx.loader.content.CursorLoader;

import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.SelectionSpec;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Load all albums (grouped by bucket_id) into a single cursor.
 */
public class AlbumLoader extends CursorLoader {

    private static final String COLUMN_BUCKET_ID = "bucket_id";
    private static final String COLUMN_BUCKET_DISPLAY_NAME = "bucket_display_name";
    public static final String COLUMN_URI = "uri";
    public static final String COLUMN_COUNT = "count";
    private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");

    private static final String[] COLUMNS = {
            MediaStore.Files.FileColumns._ID,
            COLUMN_BUCKET_ID,
            COLUMN_BUCKET_DISPLAY_NAME,
            MediaStore.MediaColumns.MIME_TYPE,
            COLUMN_URI,
            COLUMN_COUNT};

    private static final String[] PROJECTION = {
            MediaStore.Files.FileColumns._ID,
            COLUMN_BUCKET_ID,
            COLUMN_BUCKET_DISPLAY_NAME,
            MediaStore.MediaColumns.MIME_TYPE,
            "COUNT(*) AS " + COLUMN_COUNT};

    private static final String[] PROJECTION_29 = {
            MediaStore.Files.FileColumns._ID,
            COLUMN_BUCKET_ID,
            COLUMN_BUCKET_DISPLAY_NAME,
            MediaStore.MediaColumns.MIME_TYPE};

    // === params for showSingleMediaType: false ===
    private static final String SELECTION =
            "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " OR "
                    + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0"
                    + ") GROUP BY (bucket_id";
    private static final String SELECTION_29 =
            "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " OR "
                    + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0";
    private static final String[] SELECTION_ARGS = {
            String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
            String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
    };
    // =============================================

    // === params for showSingleMediaType: true ===
    private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE =
            MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0"
                    + ") GROUP BY (bucket_id";
    private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE_29 =
            MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0";

    private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
        return new String[]{String.valueOf(mediaType)};
    }
    // =============================================

    // === params for showSingleMediaType: true ===
    private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE =
            MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0"
                    + " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?"
                    + ") GROUP BY (bucket_id";
    private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29 =
            MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0"
                    + " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?";

    private static String[] getSelectionArgsForSingleMediaGifType(int mediaType) {
        return new String[]{String.valueOf(mediaType), "image/gif"};
    }
    // =============================================

    private static final String BUCKET_ORDER_BY = "datetaken DESC";

    private AlbumLoader(Context context, String selection, String[] selectionArgs) {
        super(
                context,
                QUERY_URI,
                beforeAndroidTen() ? PROJECTION : PROJECTION_29,
                selection,
                selectionArgs,
                BUCKET_ORDER_BY
        );
    }

    public static CursorLoader newInstance(Context context) {
        String selection;
        String[] selectionArgs;
        if (SelectionSpec.getInstance().onlyShowGif()) {
            selection = beforeAndroidTen()
                    ? SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE : SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29;
            selectionArgs = getSelectionArgsForSingleMediaGifType(
                    MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
        } else if (SelectionSpec.getInstance().onlyShowImages()) {
            selection = beforeAndroidTen()
                    ? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
            selectionArgs = getSelectionArgsForSingleMediaType(
                    MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
        } else if (SelectionSpec.getInstance().onlyShowVideos()) {
            selection = beforeAndroidTen()
                    ? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
            selectionArgs = getSelectionArgsForSingleMediaType(
                    MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
        } else {
            selection = beforeAndroidTen() ? SELECTION : SELECTION_29;
            selectionArgs = SELECTION_ARGS;
        }
        return new AlbumLoader(context, selection, selectionArgs);
    }

    @Override
    public Cursor loadInBackground() {
        Cursor albums = super.loadInBackground();
        MatrixCursor allAlbum = new MatrixCursor(COLUMNS);

        if (beforeAndroidTen()) {
            int totalCount = 0;
            Uri allAlbumCoverUri = null;
            MatrixCursor otherAlbums = new MatrixCursor(COLUMNS);
            if (albums != null) {
                while (albums.moveToNext()) {
                    long fileId = albums.getLong(
                            albums.getColumnIndex(MediaStore.Files.FileColumns._ID));
                    long bucketId = albums.getLong(
                            albums.getColumnIndex(COLUMN_BUCKET_ID));
                    String bucketDisplayName = albums.getString(
                            albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
                    String mimeType = albums.getString(
                            albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
                    Uri uri = getUri(albums);
                    int count = albums.getInt(albums.getColumnIndex(COLUMN_COUNT));

                    otherAlbums.addRow(new String[]{
                            Long.toString(fileId),
                            Long.toString(bucketId), bucketDisplayName, mimeType, uri.toString(),
                            String.valueOf(count)});
                    totalCount += count;
                }
                if (albums.moveToFirst()) {
                    allAlbumCoverUri = getUri(albums);
                }
            }

            allAlbum.addRow(new String[]{
                    Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null,
                    allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(),
                    String.valueOf(totalCount)});

            return new MergeCursor(new Cursor[]{allAlbum, otherAlbums});
        } else {
            int totalCount = 0;
            Uri allAlbumCoverUri = null;

            // Pseudo GROUP BY
            Map<Long, Long> countMap = new HashMap<>();
            if (albums != null) {
                while (albums.moveToNext()) {
                    long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));

                    Long count = countMap.get(bucketId);
                    if (count == null) {
                        count = 1L;
                    } else {
                        count++;
                    }
                    countMap.put(bucketId, count);
                }
            }

            MatrixCursor otherAlbums = new MatrixCursor(COLUMNS);
            if (albums != null) {
                if (albums.moveToFirst()) {
                    allAlbumCoverUri = getUri(albums);

                    Set<Long> done = new HashSet<>();

                    do {
                        long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));

                        if (done.contains(bucketId)) {
                            continue;
                        }

                        long fileId = albums.getLong(
                                albums.getColumnIndex(MediaStore.Files.FileColumns._ID));
                        String bucketDisplayName = albums.getString(
                                albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
                        String mimeType = albums.getString(
                                albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
                        Uri uri = getUri(albums);
                        long count = countMap.get(bucketId);

                        otherAlbums.addRow(new String[]{
                                Long.toString(fileId),
                                Long.toString(bucketId),
                                bucketDisplayName,
                                mimeType,
                                uri.toString(),
                                String.valueOf(count)});
                        done.add(bucketId);

                        totalCount += count;
                    } while (albums.moveToNext());
                }
            }

            allAlbum.addRow(new String[]{
                    Album.ALBUM_ID_ALL,
                    Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null,
                    allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(),
                    String.valueOf(totalCount)});

            return new MergeCursor(new Cursor[]{allAlbum, otherAlbums});
        }
    }

    private static Uri getUri(Cursor cursor) {
        long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
        String mimeType = cursor.getString(
                cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
        Uri contentUri;

        if (MimeType.isImage(mimeType)) {
            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        } else if (MimeType.isVideo(mimeType)) {
            contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        } else {
            // ?
            contentUri = MediaStore.Files.getContentUri("external");
        }

        Uri uri = ContentUris.withAppendedId(contentUri, id);
        return uri;
    }

    @Override
    public void onContentChanged() {
        // FIXME a dirty way to fix loading multiple times
    }

    /**
     * @return 是否是 Android 10 (Q) 之前的版本
     */
    private static boolean beforeAndroidTen() {
        return android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;
    }
}

================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.loader;

import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.provider.MediaStore;

import androidx.loader.content.CursorLoader;

import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.utils.MediaStoreCompat;

/**
 * Load images and videos into a single cursor.
 */
public class AlbumMediaLoader extends CursorLoader {
    private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
    private static final String[] PROJECTION = {
            MediaStore.Files.FileColumns._ID,
            MediaStore.MediaColumns.DISPLAY_NAME,
            MediaStore.MediaColumns.MIME_TYPE,
            MediaStore.MediaColumns.SIZE,
            "duration"};

    // === params for album ALL && showSingleMediaType: false ===
    private static final String SELECTION_ALL =
            "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " OR "
                    + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0";
    private static final String[] SELECTION_ALL_ARGS = {
            String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
            String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
    };
    // ===========================================================

    // === params for album ALL && showSingleMediaType: true ===
    private static final String SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE =
            MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0";

    private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
        return new String[]{String.valueOf(mediaType)};
    }
    // =========================================================

    // === params for ordinary album && showSingleMediaType: false ===
    private static final String SELECTION_ALBUM =
            "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " OR "
                    + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
                    + " AND "
                    + " bucket_id=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0";

    private static String[] getSelectionAlbumArgs(String albumId) {
        return new String[]{
                String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
                String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
                albumId
        };
    }
    // ===============================================================

    // === params for ordinary album && showSingleMediaType: true ===
    private static final String SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE =
            MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " AND "
                    + " bucket_id=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0";

    private static String[] getSelectionAlbumArgsForSingleMediaType(int mediaType, String albumId) {
        return new String[]{String.valueOf(mediaType), albumId};
    }
    // ===============================================================

    // === params for album ALL && showSingleMediaType: true && MineType=="image/gif"
    private static final String SELECTION_ALL_FOR_GIF =
            MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " AND "
                    + MediaStore.MediaColumns.MIME_TYPE + "=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0";

    private static String[] getSelectionArgsForGifType(int mediaType) {
        return new String[]{String.valueOf(mediaType), "image/gif"};
    }
    // ===============================================================

    // === params for ordinary album && showSingleMediaType: true  && MineType=="image/gif" ===
    private static final String SELECTION_ALBUM_FOR_GIF =
            MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " AND "
                    + " bucket_id=?"
                    + " AND "
                    + MediaStore.MediaColumns.MIME_TYPE + "=?"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0";

    private static String[] getSelectionAlbumArgsForGifType(int mediaType, String albumId) {
        return new String[]{String.valueOf(mediaType), albumId, "image/gif"};
    }
    // ===============================================================

    private static final String ORDER_BY = MediaStore.Images.Media.DATE_TAKEN + " DESC";
    private final boolean mEnableCapture;

    private AlbumMediaLoader(Context context, String selection, String[] selectionArgs, boolean capture) {
        super(context, QUERY_URI, PROJECTION, selection, selectionArgs, ORDER_BY);
        mEnableCapture = capture;
    }

    public static CursorLoader newInstance(Context context, Album album, boolean capture) {
        String selection;
        String[] selectionArgs;
        boolean enableCapture;

        if (album.isAll()) {
            if (SelectionSpec.getInstance().onlyShowGif()) {
                selection = SELECTION_ALL_FOR_GIF;
                selectionArgs = getSelectionArgsForGifType(
                        MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
            } else if (SelectionSpec.getInstance().onlyShowImages()) {
                selection = SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE;
                selectionArgs =
                        getSelectionArgsForSingleMediaType(
                                MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
            } else if (SelectionSpec.getInstance().onlyShowVideos()) {
                selection = SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE;
                selectionArgs =
                        getSelectionArgsForSingleMediaType(
                                MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
            } else {
                selection = SELECTION_ALL;
                selectionArgs = SELECTION_ALL_ARGS;
            }
            enableCapture = capture;
        } else {
            if (SelectionSpec.getInstance().onlyShowGif()) {
                selection = SELECTION_ALBUM_FOR_GIF;
                selectionArgs =
                        getSelectionAlbumArgsForGifType(
                                MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE, album.getId());
            } else if (SelectionSpec.getInstance().onlyShowImages()) {
                selection = SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE;
                selectionArgs =
                        getSelectionAlbumArgsForSingleMediaType(
                                MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE,
                                album.getId());
            } else if (SelectionSpec.getInstance().onlyShowVideos()) {
                selection = SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE;
                selectionArgs = getSelectionAlbumArgsForSingleMediaType(
                        MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO,
                        album.getId());
            } else {
                selection = SELECTION_ALBUM;
                selectionArgs = getSelectionAlbumArgs(album.getId());
            }
            enableCapture = false;
        }
        return new AlbumMediaLoader(context, selection, selectionArgs, enableCapture);
    }

    @Override
    public Cursor loadInBackground() {
        Cursor result = super.loadInBackground();
        if (!mEnableCapture || !MediaStoreCompat.hasCameraFeature(getContext())) {
            return result;
        }
        MatrixCursor dummy = new MatrixCursor(PROJECTION);
        dummy.addRow(new Object[]{Item.ITEM_ID_CAPTURE, Item.ITEM_DISPLAY_NAME_CAPTURE, "", 0, 0});
        return new MergeCursor(new Cursor[]{dummy, result});
    }

    @Override
    public void onContentChanged() {
        // FIXME a dirty way to fix loading multiple times
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.model;

import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;

import com.zhihu.matisse.internal.loader.AlbumLoader;

import java.lang.ref.WeakReference;

public class AlbumCollection implements LoaderManager.LoaderCallbacks<Cursor> {
    private static final int LOADER_ID = 1;
    private static final String STATE_CURRENT_SELECTION = "state_current_selection";
    private WeakReference<Context> mContext;
    private LoaderManager mLoaderManager;
    private AlbumCallbacks mCallbacks;
    private int mCurrentSelection;
    private boolean mLoadFinished;

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Context context = mContext.get();
        if (context == null) {
            return null;
        }
        mLoadFinished = false;
        return AlbumLoader.newInstance(context);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        Context context = mContext.get();
        if (context == null) {
            return;
        }

        if (!mLoadFinished) {
            mLoadFinished = true;
            mCallbacks.onAlbumLoad(data);
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        Context context = mContext.get();
        if (context == null) {
            return;
        }

        mCallbacks.onAlbumReset();
    }

    public void onCreate(FragmentActivity activity, AlbumCallbacks callbacks) {
        mContext = new WeakReference<Context>(activity);
        mLoaderManager = activity.getSupportLoaderManager();
        mCallbacks = callbacks;
    }

    public void onRestoreInstanceState(Bundle savedInstanceState) {
        if (savedInstanceState == null) {
            return;
        }

        mCurrentSelection = savedInstanceState.getInt(STATE_CURRENT_SELECTION);
    }

    public void onSaveInstanceState(Bundle outState) {
        outState.putInt(STATE_CURRENT_SELECTION, mCurrentSelection);
    }

    public void onDestroy() {
        if (mLoaderManager != null) {
            mLoaderManager.destroyLoader(LOADER_ID);
        }
        mCallbacks = null;
    }

    public void loadAlbums() {
        mLoaderManager.initLoader(LOADER_ID, null, this);
    }

    public int getCurrentSelection() {
        return mCurrentSelection;
    }

    public void setStateCurrentSelection(int currentSelection) {
        mCurrentSelection = currentSelection;
    }

    public interface AlbumCallbacks {
        void onAlbumLoad(Cursor cursor);

        void onAlbumReset();
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java
================================================
/*
 * Copyright (C) 2014 nohana, Inc.
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.model;

import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;

import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.loader.AlbumMediaLoader;

import java.lang.ref.WeakReference;

public class AlbumMediaCollection implements LoaderManager.LoaderCallbacks<Cursor> {
    private static final int LOADER_ID = 2;
    private static final String ARGS_ALBUM = "args_album";
    private static final String ARGS_ENABLE_CAPTURE = "args_enable_capture";
    private WeakReference<Context> mContext;
    private LoaderManager mLoaderManager;
    private AlbumMediaCallbacks mCallbacks;

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Context context = mContext.get();
        if (context == null) {
            return null;
        }

        Album album = args.getParcelable(ARGS_ALBUM);
        if (album == null) {
            return null;
        }

        return AlbumMediaLoader.newInstance(context, album,
                album.isAll() && args.getBoolean(ARGS_ENABLE_CAPTURE, false));
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        Context context = mContext.get();
        if (context == null) {
            return;
        }

        mCallbacks.onAlbumMediaLoad(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        Context context = mContext.get();
        if (context == null) {
            return;
        }

        mCallbacks.onAlbumMediaReset();
    }

    public void onCreate(@NonNull FragmentActivity context, @NonNull AlbumMediaCallbacks callbacks) {
        mContext = new WeakReference<Context>(context);
        mLoaderManager = context.getSupportLoaderManager();
        mCallbacks = callbacks;
    }

    public void onDestroy() {
        if (mLoaderManager != null) {
            mLoaderManager.destroyLoader(LOADER_ID);
        }
        mCallbacks = null;
    }

    public void load(@Nullable Album target) {
        load(target, false);
    }

    public void load(@Nullable Album target, boolean enableCapture) {
        Bundle args = new Bundle();
        args.putParcelable(ARGS_ALBUM, target);
        args.putBoolean(ARGS_ENABLE_CAPTURE, enableCapture);
        mLoaderManager.initLoader(LOADER_ID, args, this);
    }

    public interface AlbumMediaCallbacks {

        void onAlbumMediaLoad(Cursor cursor);

        void onAlbumMediaReset();
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.model;

import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;

import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.IncapableCause;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.ui.widget.CheckView;
import com.zhihu.matisse.internal.utils.PathUtils;
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

@SuppressWarnings("unused")
public class SelectedItemCollection {

    public static final String STATE_SELECTION = "state_selection";
    public static final String STATE_COLLECTION_TYPE = "state_collection_type";
    /**
     * Empty collection
     */
    public static final int COLLECTION_UNDEFINED = 0x00;
    /**
     * Collection only with images
     */
    public static final int COLLECTION_IMAGE = 0x01;
    /**
     * Collection only with videos
     */
    public static final int COLLECTION_VIDEO = 0x01 << 1;
    /**
     * Collection with images and videos.
     */
    public static final int COLLECTION_MIXED = COLLECTION_IMAGE | COLLECTION_VIDEO;
    private final Context mContext;
    private Set<Item> mItems;
    private int mCollectionType = COLLECTION_UNDEFINED;

    public SelectedItemCollection(Context context) {
        mContext = context;
    }

    public void onCreate(Bundle bundle) {
        if (bundle == null) {
            mItems = new LinkedHashSet<>();
        } else {
            List<Item> saved = bundle.getParcelableArrayList(STATE_SELECTION);
            mItems = new LinkedHashSet<>(saved);
            mCollectionType = bundle.getInt(STATE_COLLECTION_TYPE, COLLECTION_UNDEFINED);
        }
    }

    public void setDefaultSelection(List<Item> uris) {
        mItems.addAll(uris);
    }

    public void onSaveInstanceState(Bundle outState) {
        outState.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems));
        outState.putInt(STATE_COLLECTION_TYPE, mCollectionType);
    }

    public Bundle getDataWithBundle() {
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems));
        bundle.putInt(STATE_COLLECTION_TYPE, mCollectionType);
        return bundle;
    }

    public boolean add(Item item) {
        if (typeConflict(item)) {
            throw new IllegalArgumentException("Can't select images and videos at the same time.");
        }
        boolean added = mItems.add(item);
        if (added) {
            if (mCollectionType == COLLECTION_UNDEFINED) {
                if (item.isImage()) {
                    mCollectionType = COLLECTION_IMAGE;
                } else if (item.isVideo()) {
                    mCollectionType = COLLECTION_VIDEO;
                }
            } else if (mCollectionType == COLLECTION_IMAGE) {
                if (item.isVideo()) {
                    mCollectionType = COLLECTION_MIXED;
                }
            } else if (mCollectionType == COLLECTION_VIDEO) {
                if (item.isImage()) {
                    mCollectionType = COLLECTION_MIXED;
                }
            }
        }
        return added;
    }

    public boolean remove(Item item) {
        boolean removed = mItems.remove(item);
        if (removed) {
            if (mItems.size() == 0) {
                mCollectionType = COLLECTION_UNDEFINED;
            } else {
                if (mCollectionType == COLLECTION_MIXED) {
                    refineCollectionType();
                }
            }
        }
        return removed;
    }

    public void overwrite(ArrayList<Item> items, int collectionType) {
        if (items.size() == 0) {
            mCollectionType = COLLECTION_UNDEFINED;
        } else {
            mCollectionType = collectionType;
        }
        mItems.clear();
        mItems.addAll(items);
    }


    public List<Item> asList() {
        return new ArrayList<>(mItems);
    }

    public List<Uri> asListOfUri() {
        List<Uri> uris = new ArrayList<>();
        for (Item item : mItems) {
            uris.add(item.getContentUri());
        }
        return uris;
    }

    public List<String> asListOfString() {
        List<String> paths = new ArrayList<>();
        for (Item item : mItems) {
            paths.add(PathUtils.getPath(mContext, item.getContentUri()));
        }
        return paths;
    }

    public boolean isEmpty() {
        return mItems == null || mItems.isEmpty();
    }

    public boolean isSelected(Item item) {
        return mItems.contains(item);
    }

    public IncapableCause isAcceptable(Item item) {
        if (maxSelectableReached()) {
            int maxSelectable = currentMaxSelectable();
            String cause;

            try {
                cause = mContext.getResources().getQuantityString(
                        R.plurals.error_over_count,
                        maxSelectable,
                        maxSelectable
                );
            } catch (Resources.NotFoundException e) {
                cause = mContext.getString(
                        R.string.error_over_count,
                        maxSelectable
                );
            } catch (NoClassDefFoundError e) {
                cause = mContext.getString(
                        R.string.error_over_count,
                        maxSelectable
                );
            }

            return new IncapableCause(cause);
        } else if (typeConflict(item)) {
            return new IncapableCause(mContext.getString(R.string.error_type_conflict));
        }

        return PhotoMetadataUtils.isAcceptable(mContext, item);
    }

    public boolean maxSelectableReached() {
        return mItems.size() == currentMaxSelectable();
    }

    // depends
    private int currentMaxSelectable() {
        SelectionSpec spec = SelectionSpec.getInstance();
        if (spec.maxSelectable > 0) {
            return spec.maxSelectable;
        } else if (mCollectionType == COLLECTION_IMAGE) {
            return spec.maxImageSelectable;
        } else if (mCollectionType == COLLECTION_VIDEO) {
            return spec.maxVideoSelectable;
        } else {
            return spec.maxSelectable;
        }
    }

    public int getCollectionType() {
        return mCollectionType;
    }

    private void refineCollectionType() {
        boolean hasImage = false;
        boolean hasVideo = false;
        for (Item i : mItems) {
            if (i.isImage() && !hasImage) hasImage = true;
            if (i.isVideo() && !hasVideo) hasVideo = true;
        }
        if (hasImage && hasVideo) {
            mCollectionType = COLLECTION_MIXED;
        } else if (hasImage) {
            mCollectionType = COLLECTION_IMAGE;
        } else if (hasVideo) {
            mCollectionType = COLLECTION_VIDEO;
        }
    }

    /**
     * Determine whether there will be conflict media types. A user can only select images and videos at the same time
     * while {@link SelectionSpec#mediaTypeExclusive} is set to false.
     */
    public boolean typeConflict(Item item) {
        return SelectionSpec.getInstance().mediaTypeExclusive
                && ((item.isImage() && (mCollectionType == COLLECTION_VIDEO || mCollectionType == COLLECTION_MIXED))
                || (item.isVideo() && (mCollectionType == COLLECTION_IMAGE || mCollectionType == COLLECTION_MIXED)));
    }

    public int count() {
        return mItems.size();
    }

    public int checkedNumOf(Item item) {
        int index = new ArrayList<>(mItems).indexOf(item);
        return index == -1 ? CheckView.UNCHECKED : index + 1;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui;

import android.database.Cursor;
import android.os.Bundle;
import androidx.annotation.Nullable;

import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.model.AlbumMediaCollection;
import com.zhihu.matisse.internal.ui.adapter.PreviewPagerAdapter;

import java.util.ArrayList;
import java.util.List;

public class AlbumPreviewActivity extends BasePreviewActivity implements
        AlbumMediaCollection.AlbumMediaCallbacks {

    public static final String EXTRA_ALBUM = "extra_album";
    public static final String EXTRA_ITEM = "extra_item";

    private AlbumMediaCollection mCollection = new AlbumMediaCollection();

    private boolean mIsAlreadySetPosition;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!SelectionSpec.getInstance().hasInited) {
            setResult(RESULT_CANCELED);
            finish();
            return;
        }
        mCollection.onCreate(this, this);
        Album album = getIntent().getParcelableExtra(EXTRA_ALBUM);
        mCollection.load(album);

        Item item = getIntent().getParcelableExtra(EXTRA_ITEM);
        if (mSpec.countable) {
            mCheckView.setCheckedNum(mSelectedCollection.checkedNumOf(item));
        } else {
            mCheckView.setChecked(mSelectedCollection.isSelected(item));
        }
        updateSize(item);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mCollection.onDestroy();
    }

    @Override
    public void onAlbumMediaLoad(Cursor cursor) {
        List<Item> items = new ArrayList<>();
        while (cursor.moveToNext()) {
            items.add(Item.valueOf(cursor));
        }
//        cursor.close();

        if (items.isEmpty()) {
            return;
        }

        PreviewPagerAdapter adapter = (PreviewPagerAdapter) mPager.getAdapter();
        adapter.addAll(items);
        adapter.notifyDataSetChanged();
        if (!mIsAlreadySetPosition) {
            //onAlbumMediaLoad is called many times..
            mIsAlreadySetPosition = true;
            Item selected = getIntent().getParcelableExtra(EXTRA_ITEM);
            int selectedIndex = items.indexOf(selected);
            mPager.setCurrentItem(selectedIndex, false);
            mPreviousPos = selectedIndex;
        }
    }

    @Override
    public void onAlbumMediaReset() {

    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.IncapableCause;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.model.SelectedItemCollection;
import com.zhihu.matisse.internal.ui.adapter.PreviewPagerAdapter;
import com.zhihu.matisse.internal.ui.widget.CheckRadioView;
import com.zhihu.matisse.internal.ui.widget.CheckView;
import com.zhihu.matisse.internal.ui.widget.IncapableDialog;
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;
import com.zhihu.matisse.internal.utils.Platform;
import com.zhihu.matisse.listener.OnFragmentInteractionListener;

public abstract class BasePreviewActivity extends AppCompatActivity implements View.OnClickListener,
        ViewPager.OnPageChangeListener, OnFragmentInteractionListener {

    public static final String EXTRA_DEFAULT_BUNDLE = "extra_default_bundle";
    public static final String EXTRA_RESULT_BUNDLE = "extra_result_bundle";
    public static final String EXTRA_RESULT_APPLY = "extra_result_apply";
    public static final String EXTRA_RESULT_ORIGINAL_ENABLE = "extra_result_original_enable";
    public static final String CHECK_STATE = "checkState";

    protected final SelectedItemCollection mSelectedCollection = new SelectedItemCollection(this);
    protected SelectionSpec mSpec;
    protected ViewPager mPager;

    protected PreviewPagerAdapter mAdapter;

    protected CheckView mCheckView;
    protected TextView mButtonBack;
    protected TextView mButtonApply;
    protected TextView mSize;

    protected int mPreviousPos = -1;

    private LinearLayout mOriginalLayout;
    private CheckRadioView mOriginal;
    protected boolean mOriginalEnable;

    private FrameLayout mBottomToolbar;
    private FrameLayout mTopToolbar;
    private boolean mIsToolbarHide = false;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        setTheme(SelectionSpec.getInstance().themeId);
        super.onCreate(savedInstanceState);
        if (!SelectionSpec.getInstance().hasInited) {
            setResult(RESULT_CANCELED);
            finish();
            return;
        }
        setContentView(R.layout.activity_media_preview);
        if (Platform.hasKitKat()) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }

        mSpec = SelectionSpec.getInstance();
        if (mSpec.needOrientationRestriction()) {
            setRequestedOrientation(mSpec.orientation);
        }

        if (savedInstanceState == null) {
            mSelectedCollection.onCreate(getIntent().getBundleExtra(EXTRA_DEFAULT_BUNDLE));
            mOriginalEnable = getIntent().getBooleanExtra(EXTRA_RESULT_ORIGINAL_ENABLE, false);
        } else {
            mSelectedCollection.onCreate(savedInstanceState);
            mOriginalEnable = savedInstanceState.getBoolean(CHECK_STATE);
        }
        mButtonBack = (TextView) findViewById(R.id.button_back);
        mButtonApply = (TextView) findViewById(R.id.button_apply);
        mSize = (TextView) findViewById(R.id.size);
        mButtonBack.setOnClickListener(this);
        mButtonApply.setOnClickListener(this);

        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.addOnPageChangeListener(this);
        mAdapter = new PreviewPagerAdapter(getSupportFragmentManager(), null);
        mPager.setAdapter(mAdapter);
        mCheckView = (CheckView) findViewById(R.id.check_view);
        mCheckView.setCountable(mSpec.countable);
        mBottomToolbar = findViewById(R.id.bottom_toolbar);
        mTopToolbar = findViewById(R.id.top_toolbar);

        mCheckView.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Item item = mAdapter.getMediaItem(mPager.getCurrentItem());
                if (mSelectedCollection.isSelected(item)) {
                    mSelectedCollection.remove(item);
                    if (mSpec.countable) {
                        mCheckView.setCheckedNum(CheckView.UNCHECKED);
                    } else {
                        mCheckView.setChecked(false);
                    }
                } else {
                    if (assertAddSelection(item)) {
                        mSelectedCollection.add(item);
                        if (mSpec.countable) {
                            mCheckView.setCheckedNum(mSelectedCollection.checkedNumOf(item));
                        } else {
                            mCheckView.setChecked(true);
                        }
                    }
                }
                updateApplyButton();

                if (mSpec.onSelectedListener != null) {
                    mSpec.onSelectedListener.onSelected(
                            mSelectedCollection.asListOfUri(), mSelectedCollection.asListOfString());
                }
            }
        });


        mOriginalLayout = findViewById(R.id.originalLayout);
        mOriginal = findViewById(R.id.original);
        mOriginalLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                int count = countOverMaxSize();
                if (count > 0) {
                    IncapableDialog incapableDialog = IncapableDialog.newInstance("",
                            getString(R.string.error_over_original_count, count, mSpec.originalMaxSize));
                    incapableDialog.show(getSupportFragmentManager(),
                            IncapableDialog.class.getName());
                    return;
                }

                mOriginalEnable = !mOriginalEnable;
                mOriginal.setChecked(mOriginalEnable);
                if (!mOriginalEnable) {
                    mOriginal.setColor(Color.WHITE);
                }


                if (mSpec.onCheckedListener != null) {
                    mSpec.onCheckedListener.onCheck(mOriginalEnable);
                }
            }
        });

        updateApplyButton();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        mSelectedCollection.onSaveInstanceState(outState);
        outState.putBoolean("checkState", mOriginalEnable);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onBackPressed() {
        sendBackResult(false);
        super.onBackPressed();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.button_back) {
            onBackPressed();
        } else if (v.getId() == R.id.button_apply) {
            sendBackResult(true);
            finish();
        }
    }

    @Override
    public void onClick() {
        if (!mSpec.autoHideToobar) {
            return;
        }

        if (mIsToolbarHide) {
            mTopToolbar.animate()
                    .setInterpolator(new FastOutSlowInInterpolator())
                    .translationYBy(mTopToolbar.getMeasuredHeight())
                    .start();
            mBottomToolbar.animate()
                    .translationYBy(-mBottomToolbar.getMeasuredHeight())
                    .setInterpolator(new FastOutSlowInInterpolator())
                    .start();
        } else {
            mTopToolbar.animate()
                    .setInterpolator(new FastOutSlowInInterpolator())
                    .translationYBy(-mTopToolbar.getMeasuredHeight())
                    .start();
            mBottomToolbar.animate()
                    .setInterpolator(new FastOutSlowInInterpolator())
                    .translationYBy(mBottomToolbar.getMeasuredHeight())
                    .start();
        }

        mIsToolbarHide = !mIsToolbarHide;

    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        PreviewPagerAdapter adapter = (PreviewPagerAdapter) mPager.getAdapter();
        if (mPreviousPos != -1 && mPreviousPos != position) {
            ((PreviewItemFragment) adapter.instantiateItem(mPager, mPreviousPos)).resetView();

            Item item = adapter.getMediaItem(position);
            if (mSpec.countable) {
                int checkedNum = mSelectedCollection.checkedNumOf(item);
                mCheckView.setCheckedNum(checkedNum);
                if (checkedNum > 0) {
                    mCheckView.setEnabled(true);
                } else {
                    mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached());
                }
            } else {
                boolean checked = mSelectedCollection.isSelected(item);
                mCheckView.setChecked(checked);
                if (checked) {
                    mCheckView.setEnabled(true);
                } else {
                    mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached());
                }
            }
            updateSize(item);
        }
        mPreviousPos = position;
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

    private void updateApplyButton() {
        int selectedCount = mSelectedCollection.count();
        if (selectedCount == 0) {
            mButtonApply.setText(R.string.button_apply_default);
            mButtonApply.setEnabled(false);
        } else if (selectedCount == 1 && mSpec.singleSelectionModeEnabled()) {
            mButtonApply.setText(R.string.button_apply_default);
            mButtonApply.setEnabled(true);
        } else {
            mButtonApply.setEnabled(true);
            mButtonApply.setText(getString(R.string.button_apply, selectedCount));
        }

        if (mSpec.originalable) {
            mOriginalLayout.setVisibility(View.VISIBLE);
            updateOriginalState();
        } else {
            mOriginalLayout.setVisibility(View.GONE);
        }
    }


    private void updateOriginalState() {
        mOriginal.setChecked(mOriginalEnable);
        if (!mOriginalEnable) {
            mOriginal.setColor(Color.WHITE);
        }

        if (countOverMaxSize() > 0) {

            if (mOriginalEnable) {
                IncapableDialog incapableDialog = IncapableDialog.newInstance("",
                        getString(R.string.error_over_original_size, mSpec.originalMaxSize));
                incapableDialog.show(getSupportFragmentManager(),
                        IncapableDialog.class.getName());

                mOriginal.setChecked(false);
                mOriginal.setColor(Color.WHITE);
                mOriginalEnable = false;
            }
        }
    }


    private int countOverMaxSize() {
        int count = 0;
        int selectedCount = mSelectedCollection.count();
        for (int i = 0; i < selectedCount; i++) {
            Item item = mSelectedCollection.asList().get(i);
            if (item.isImage()) {
                float size = PhotoMetadataUtils.getSizeInMB(item.size);
                if (size > mSpec.originalMaxSize) {
                    count++;
                }
            }
        }
        return count;
    }

    protected void updateSize(Item item) {
        if (item.isGif()) {
            mSize.setVisibility(View.VISIBLE);
            mSize.setText(PhotoMetadataUtils.getSizeInMB(item.size) + "M");
        } else {
            mSize.setVisibility(View.GONE);
        }

        if (item.isVideo()) {
            mOriginalLayout.setVisibility(View.GONE);
        } else if (mSpec.originalable) {
            mOriginalLayout.setVisibility(View.VISIBLE);
        }
    }

    protected void sendBackResult(boolean apply) {
        Intent intent = new Intent();
        intent.putExtra(EXTRA_RESULT_BUNDLE, mSelectedCollection.getDataWithBundle());
        intent.putExtra(EXTRA_RESULT_APPLY, apply);
        intent.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
        setResult(Activity.RESULT_OK, intent);
    }

    private boolean assertAddSelection(Item item) {
        IncapableCause cause = mSelectedCollection.isAcceptable(item);
        IncapableCause.handleCause(this, cause);
        return cause == null;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui;

import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.model.AlbumMediaCollection;
import com.zhihu.matisse.internal.model.SelectedItemCollection;
import com.zhihu.matisse.internal.ui.adapter.AlbumMediaAdapter;
import com.zhihu.matisse.internal.ui.widget.MediaGridInset;
import com.zhihu.matisse.internal.utils.UIUtils;

public class MediaSelectionFragment extends Fragment implements
        AlbumMediaCollection.AlbumMediaCallbacks, AlbumMediaAdapter.CheckStateListener,
        AlbumMediaAdapter.OnMediaClickListener {

    public static final String EXTRA_ALBUM = "extra_album";

    private final AlbumMediaCollection mAlbumMediaCollection = new AlbumMediaCollection();
    private RecyclerView mRecyclerView;
    private AlbumMediaAdapter mAdapter;
    private SelectionProvider mSelectionProvider;
    private AlbumMediaAdapter.CheckStateListener mCheckStateListener;
    private AlbumMediaAdapter.OnMediaClickListener mOnMediaClickListener;

    public static MediaSelectionFragment newInstance(Album album) {
        MediaSelectionFragment fragment = new MediaSelectionFragment();
        Bundle args = new Bundle();
        args.putParcelable(EXTRA_ALBUM, album);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof SelectionProvider) {
            mSelectionProvider = (SelectionProvider) context;
        } else {
            throw new IllegalStateException("Context must implement SelectionProvider.");
        }
        if (context instanceof AlbumMediaAdapter.CheckStateListener) {
            mCheckStateListener = (AlbumMediaAdapter.CheckStateListener) context;
        }
        if (context instanceof AlbumMediaAdapter.OnMediaClickListener) {
            mOnMediaClickListener = (AlbumMediaAdapter.OnMediaClickListener) context;
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_media_selection, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Album album = getArguments().getParcelable(EXTRA_ALBUM);

        mAdapter = new AlbumMediaAdapter(getContext(),
                mSelectionProvider.provideSelectedItemCollection(), mRecyclerView);
        mAdapter.registerCheckStateListener(this);
        mAdapter.registerOnMediaClickListener(this);
        mRecyclerView.setHasFixedSize(true);

        int spanCount;
        SelectionSpec selectionSpec = SelectionSpec.getInstance();
        if (selectionSpec.gridExpectedSize > 0) {
            spanCount = UIUtils.spanCount(getContext(), selectionSpec.gridExpectedSize);
        } else {
            spanCount = selectionSpec.spanCount;
        }
        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), spanCount));

        int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing);
        mRecyclerView.addItemDecoration(new MediaGridInset(spanCount, spacing, false));
        mRecyclerView.setAdapter(mAdapter);
        mAlbumMediaCollection.onCreate(getActivity(), this);
        mAlbumMediaCollection.load(album, selectionSpec.capture);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mAlbumMediaCollection.onDestroy();
    }

    public void refreshMediaGrid() {
        mAdapter.notifyDataSetChanged();
    }

    public void refreshSelection() {
        mAdapter.refreshSelection();
    }

    @Override
    public void onAlbumMediaLoad(Cursor cursor) {
        mAdapter.swapCursor(cursor);
    }

    @Override
    public void onAlbumMediaReset() {
        mAdapter.swapCursor(null);
    }

    @Override
    public void onUpdate() {
        // notify outer Activity that check state changed
        if (mCheckStateListener != null) {
            mCheckStateListener.onUpdate();
        }
    }

    @Override
    public void onMediaClick(Album album, Item item, int adapterPosition) {
        if (mOnMediaClickListener != null) {
            mOnMediaClickListener.onMediaClick((Album) getArguments().getParcelable(EXTRA_ALBUM),
                    item, adapterPosition);
        }
    }

    public interface SelectionProvider {
        SelectedItemCollection provideSelectedItemCollection();
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;
import com.zhihu.matisse.listener.OnFragmentInteractionListener;

import it.sephiroth.android.library.imagezoom.ImageViewTouch;
import it.sephiroth.android.library.imagezoom.ImageViewTouchBase;

public class PreviewItemFragment extends Fragment {

    private static final String ARGS_ITEM = "args_item";
    private OnFragmentInteractionListener mListener;

    public static PreviewItemFragment newInstance(Item item) {
        PreviewItemFragment fragment = new PreviewItemFragment();
        Bundle bundle = new Bundle();
        bundle.putParcelable(ARGS_ITEM, item);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_preview_item, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        final Item item = getArguments().getParcelable(ARGS_ITEM);
        if (item == null) {
            return;
        }

        View videoPlayButton = view.findViewById(R.id.video_play_button);
        if (item.isVideo()) {
            videoPlayButton.setVisibility(View.VISIBLE);
            videoPlayButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setDataAndType(item.uri, "video/*");
                    try {
                        startActivity(intent);
                    } catch (ActivityNotFoundException e) {
                        Toast.makeText(getContext(), R.string.error_no_video_activity, Toast.LENGTH_SHORT).show();
                    }
                }
            });
        } else {
            videoPlayButton.setVisibility(View.GONE);
        }

        ImageViewTouch image = (ImageViewTouch) view.findViewById(R.id.image_view);
        image.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN);

        image.setSingleTapListener(new ImageViewTouch.OnImageViewTouchSingleTapListener() {
            @Override
            public void onSingleTapConfirmed() {
                if (mListener != null) {
                    mListener.onClick();
                }
            }
        });

        Point size = PhotoMetadataUtils.getBitmapSize(item.getContentUri(), getActivity());
        if (item.isGif()) {
            SelectionSpec.getInstance().imageEngine.loadGifImage(getContext(), size.x, size.y, image,
                    item.getContentUri());
        } else {
            SelectionSpec.getInstance().imageEngine.loadImage(getContext(), size.x, size.y, image,
                    item.getContentUri());
        }
    }

    public void resetView() {
        if (getView() != null) {
            ((ImageViewTouch) getView().findViewById(R.id.image_view)).resetMatrix();
        }
    }


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui;

import android.os.Bundle;
import androidx.annotation.Nullable;

import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.model.SelectedItemCollection;

import java.util.List;

public class SelectedPreviewActivity extends BasePreviewActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!SelectionSpec.getInstance().hasInited) {
            setResult(RESULT_CANCELED);
            finish();
            return;
        }

        Bundle bundle = getIntent().getBundleExtra(EXTRA_DEFAULT_BUNDLE);
        List<Item> selected = bundle.getParcelableArrayList(SelectedItemCollection.STATE_SELECTION);
        mAdapter.addAll(selected);
        mAdapter.notifyDataSetChanged();
        if (mSpec.countable) {
            mCheckView.setCheckedNum(1);
        } else {
            mCheckView.setChecked(true);
        }
        mPreviousPos = 0;
        updateSize(selected.get(0));
    }

}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui.adapter;

import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.entity.IncapableCause;
import com.zhihu.matisse.internal.model.SelectedItemCollection;
import com.zhihu.matisse.internal.ui.widget.CheckView;
import com.zhihu.matisse.internal.ui.widget.MediaGrid;

public class AlbumMediaAdapter extends
        RecyclerViewCursorAdapter<RecyclerView.ViewHolder> implements
        MediaGrid.OnMediaGridClickListener {

    private static final int VIEW_TYPE_CAPTURE = 0x01;
    private static final int VIEW_TYPE_MEDIA = 0x02;
    private final SelectedItemCollection mSelectedCollection;
    private final Drawable mPlaceholder;
    private SelectionSpec mSelectionSpec;
    private CheckStateListener mCheckStateListener;
    private OnMediaClickListener mOnMediaClickListener;
    private RecyclerView mRecyclerView;
    private int mImageResize;

    public AlbumMediaAdapter(Context context, SelectedItemCollection selectedCollection, RecyclerView recyclerView) {
        super(null);
        mSelectionSpec = SelectionSpec.getInstance();
        mSelectedCollection = selectedCollection;

        TypedArray ta = context.getTheme().obtainStyledAttributes(new int[]{R.attr.item_placeholder});
        mPlaceholder = ta.getDrawable(0);
        ta.recycle();

        mRecyclerView = recyclerView;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_CAPTURE) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.photo_capture_item, parent, false);
            CaptureViewHolder holder = new CaptureViewHolder(v);
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (v.getContext() instanceof OnPhotoCapture) {
                        ((OnPhotoCapture) v.getContext()).capture();
                    }
                }
            });
            return holder;
        } else if (viewType == VIEW_TYPE_MEDIA) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.media_grid_item, parent, false);
            return new MediaViewHolder(v);
        }
        return null;
    }

    @Override
    protected void onBindViewHolder(final RecyclerView.ViewHolder holder, Cursor cursor) {
        if (holder instanceof CaptureViewHolder) {
            CaptureViewHolder captureViewHolder = (CaptureViewHolder) holder;
            Drawable[] drawables = captureViewHolder.mHint.getCompoundDrawables();
            TypedArray ta = holder.itemView.getContext().getTheme().obtainStyledAttributes(
                    new int[]{R.attr.capture_textColor});
            int color = ta.getColor(0, 0);
            ta.recycle();

            for (int i = 0; i < drawables.length; i++) {
                Drawable drawable = drawables[i];
                if (drawable != null) {
                    final Drawable.ConstantState state = drawable.getConstantState();
                    if (state == null) {
                        continue;
                    }

                    Drawable newDrawable = state.newDrawable().mutate();
                    newDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
                    newDrawable.setBounds(drawable.getBounds());
                    drawables[i] = newDrawable;
                }
            }
            captureViewHolder.mHint.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3]);
        } else if (holder instanceof MediaViewHolder) {
            MediaViewHolder mediaViewHolder = (MediaViewHolder) holder;

            final Item item = Item.valueOf(cursor);
            mediaViewHolder.mMediaGrid.preBindMedia(new MediaGrid.PreBindInfo(
                    getImageResize(mediaViewHolder.mMediaGrid.getContext()),
                    mPlaceholder,
                    mSelectionSpec.countable,
                    holder
            ));
            mediaViewHolder.mMediaGrid.bindMedia(item);
            mediaViewHolder.mMediaGrid.setOnMediaGridClickListener(this);
            setCheckStatus(item, mediaViewHolder.mMediaGrid);
        }
    }

    private void setCheckStatus(Item item, MediaGrid mediaGrid) {
        if (mSelectionSpec.countable) {
            int checkedNum = mSelectedCollection.checkedNumOf(item);
            if (checkedNum > 0) {
                mediaGrid.setCheckEnabled(true);
                mediaGrid.setCheckedNum(checkedNum);
            } else {
                if (mSelectedCollection.maxSelectableReached()) {
                    mediaGrid.setCheckEnabled(false);
                    mediaGrid.setCheckedNum(CheckView.UNCHECKED);
                } else {
                    mediaGrid.setCheckEnabled(true);
                    mediaGrid.setCheckedNum(checkedNum);
                }
            }
        } else {
            boolean selected = mSelectedCollection.isSelected(item);
            if (selected) {
                mediaGrid.setCheckEnabled(true);
                mediaGrid.setChecked(true);
            } else {
                if (mSelectedCollection.maxSelectableReached()) {
                    mediaGrid.setCheckEnabled(false);
                    mediaGrid.setChecked(false);
                } else {
                    mediaGrid.setCheckEnabled(true);
                    mediaGrid.setChecked(false);
                }
            }
        }
    }

    @Override
    public void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder) {
        if (mSelectionSpec.showPreview) {
            if (mOnMediaClickListener != null) {
                mOnMediaClickListener.onMediaClick(null, item, holder.getAdapterPosition());
            }
        } else {
            updateSelectedItem(item, holder);
        }
    }

    @Override
    public void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.ViewHolder holder) {
        updateSelectedItem(item, holder);
    }

    private void updateSelectedItem(Item item, RecyclerView.ViewHolder holder) {
        if (mSelectionSpec.countable) {
            int checkedNum = mSelectedCollection.checkedNumOf(item);
            if (checkedNum == CheckView.UNCHECKED) {
                if (assertAddSelection(holder.itemView.getContext(), item)) {
                    mSelectedCollection.add(item);
                    notifyCheckStateChanged();
                }
            } else {
                mSelectedCollection.remove(item);
                notifyCheckStateChanged();
            }
        } else {
            if (mSelectedCollection.isSelected(item)) {
                mSelectedCollection.remove(item);
                notifyCheckStateChanged();
            } else {
                if (assertAddSelection(holder.itemView.getContext(), item)) {
                    mSelectedCollection.add(item);
                    notifyCheckStateChanged();
                }
            }
        }
    }

    private void notifyCheckStateChanged() {
        notifyDataSetChanged();
        if (mCheckStateListener != null) {
            mCheckStateListener.onUpdate();
        }
    }

    @Override
    public int getItemViewType(int position, Cursor cursor) {
        return Item.valueOf(cursor).isCapture() ? VIEW_TYPE_CAPTURE : VIEW_TYPE_MEDIA;
    }

    private boolean assertAddSelection(Context context, Item item) {
        IncapableCause cause = mSelectedCollection.isAcceptable(item);
        IncapableCause.handleCause(context, cause);
        return cause == null;
    }


    public void registerCheckStateListener(CheckStateListener listener) {
        mCheckStateListener = listener;
    }

    public void unregisterCheckStateListener() {
        mCheckStateListener = null;
    }

    public void registerOnMediaClickListener(OnMediaClickListener listener) {
        mOnMediaClickListener = listener;
    }

    public void unregisterOnMediaClickListener() {
        mOnMediaClickListener = null;
    }

    public void refreshSelection() {
        GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
        int first = layoutManager.findFirstVisibleItemPosition();
        int last = layoutManager.findLastVisibleItemPosition();
        if (first == -1 || last == -1) {
            return;
        }
        Cursor cursor = getCursor();
        for (int i = first; i <= last; i++) {
            RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(first);
            if (holder instanceof MediaViewHolder) {
                if (cursor.moveToPosition(i)) {
                    setCheckStatus(Item.valueOf(cursor), ((MediaViewHolder) holder).mMediaGrid);
                }
            }
        }
    }

    private int getImageResize(Context context) {
        if (mImageResize == 0) {
            RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
            int spanCount = ((GridLayoutManager) lm).getSpanCount();
            int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
            int availableWidth = screenWidth - context.getResources().getDimensionPixelSize(
                    R.dimen.media_grid_spacing) * (spanCount - 1);
            mImageResize = availableWidth / spanCount;
            mImageResize = (int) (mImageResize * mSelectionSpec.thumbnailScale);
        }
        return mImageResize;
    }

    public interface CheckStateListener {
        void onUpdate();
    }

    public interface OnMediaClickListener {
        void onMediaClick(Album album, Item item, int adapterPosition);
    }

    public interface OnPhotoCapture {
        void capture();
    }

    private static class MediaViewHolder extends RecyclerView.ViewHolder {

        private MediaGrid mMediaGrid;

        MediaViewHolder(View itemView) {
            super(itemView);
            mMediaGrid = (MediaGrid) itemView;
        }
    }

    private static class CaptureViewHolder extends RecyclerView.ViewHolder {

        private TextView mHint;

        CaptureViewHolder(View itemView) {
            super(itemView);

            mHint = (TextView) itemView.findViewById(R.id.hint);
        }
    }

}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumsAdapter.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui.adapter;

import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.SelectionSpec;

public class AlbumsAdapter extends CursorAdapter {

    private final Drawable mPlaceholder;

    public AlbumsAdapter(Context context, Cursor c, boolean autoRequery) {
        super(context, c, autoRequery);

        TypedArray ta = context.getTheme().obtainStyledAttributes(
                new int[]{R.attr.album_thumbnail_placeholder});
        mPlaceholder = ta.getDrawable(0);
        ta.recycle();
    }

    public AlbumsAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);

        TypedArray ta = context.getTheme().obtainStyledAttributes(
                new int[]{R.attr.album_thumbnail_placeholder});
        mPlaceholder = ta.getDrawable(0);
        ta.recycle();
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        return LayoutInflater.from(context).inflate(R.layout.album_list_item, parent, false);
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        Album album = Album.valueOf(cursor);
        ((TextView) view.findViewById(R.id.album_name)).setText(album.getDisplayName(context));
        ((TextView) view.findViewById(R.id.album_media_count)).setText(String.valueOf(album.getCount()));

        // do not need to load animated Gif
        SelectionSpec.getInstance().imageEngine.loadThumbnail(context, context.getResources().getDimensionPixelSize(R
                        .dimen.media_grid_size), mPlaceholder,
                (ImageView) view.findViewById(R.id.album_cover), album.getCoverUri());
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui.adapter;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import android.view.ViewGroup;

import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.ui.PreviewItemFragment;

import java.util.ArrayList;
import java.util.List;

public class PreviewPagerAdapter extends FragmentPagerAdapter {

    private ArrayList<Item> mItems = new ArrayList<>();
    private OnPrimaryItemSetListener mListener;

    public PreviewPagerAdapter(FragmentManager manager, OnPrimaryItemSetListener listener) {
        super(manager);
        mListener = listener;
    }

    @Override
    public Fragment getItem(int position) {
        return PreviewItemFragment.newInstance(mItems.get(position));
    }

    @Override
    public int getCount() {
        return mItems.size();
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, object);
        if (mListener != null) {
            mListener.onPrimaryItemSet(position);
        }
    }

    public Item getMediaItem(int position) {
        return mItems.get(position);
    }

    public void addAll(List<Item> items) {
        mItems.addAll(items);
    }

    interface OnPrimaryItemSetListener {

        void onPrimaryItemSet(int position);
    }

}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui.adapter;

import android.database.Cursor;
import android.provider.MediaStore;
import androidx.recyclerview.widget.RecyclerView;

public abstract class RecyclerViewCursorAdapter<VH extends RecyclerView.ViewHolder> extends
        RecyclerView.Adapter<VH> {

    private Cursor mCursor;
    private int mRowIDColumn;

    RecyclerViewCursorAdapter(Cursor c) {
        setHasStableIds(true);
        swapCursor(c);
    }

    protected abstract void onBindViewHolder(VH holder, Cursor cursor);

    @Override
    public void onBindViewHolder(VH holder, int position) {
        if (!isDataValid(mCursor)) {
            throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state.");
        }
        if (!mCursor.moveToPosition(position)) {
            throw new IllegalStateException("Could not move cursor to position " + position
                    + " when trying to bind view holder");
        }

        onBindViewHolder(holder, mCursor);
    }

    @Override
    public int getItemViewType(int position) {
        if (!mCursor.moveToPosition(position)) {
            throw new IllegalStateException("Could not move cursor to position " + position
                    + " when trying to get item view type.");
        }
        return getItemViewType(position, mCursor);
    }

    protected abstract int getItemViewType(int position, Cursor cursor);

    @Override
    public int getItemCount() {
        if (isDataValid(mCursor)) {
            return mCursor.getCount();
        } else {
            return 0;
        }
    }

    @Override
    public long getItemId(int position) {
        if (!isDataValid(mCursor)) {
            throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
        }
        if (!mCursor.moveToPosition(position)) {
            throw new IllegalStateException("Could not move cursor to position " + position
                    + " when trying to get an item id");
        }

        return mCursor.getLong(mRowIDColumn);
    }

    public void swapCursor(Cursor newCursor) {
        if (newCursor == mCursor) {
            return;
        }

        if (newCursor != null) {
            mCursor = newCursor;
            mRowIDColumn = mCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID);
            // notify the observers about the new cursor
            notifyDataSetChanged();
        } else {
            notifyItemRangeRemoved(0, getItemCount());
            mCursor = null;
            mRowIDColumn = -1;
        }
    }

    public Cursor getCursor() {
        return mCursor;
    }

    private boolean isDataValid(Cursor cursor) {
        return cursor != null && !cursor.isClosed();
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.ListPopupWindow;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.TextView;

import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.utils.Platform;

public class AlbumsSpinner {

    private static final int MAX_SHOWN_COUNT = 6;
    private CursorAdapter mAdapter;
    private TextView mSelected;
    private ListPopupWindow mListPopupWindow;
    private AdapterView.OnItemSelectedListener mOnItemSelectedListener;

    public AlbumsSpinner(@NonNull Context context) {
        mListPopupWindow = new ListPopupWindow(context, null, R.attr.listPopupWindowStyle);
        mListPopupWindow.setModal(true);
        float density = context.getResources().getDisplayMetrics().density;
        mListPopupWindow.setContentWidth((int) (216 * density));
        mListPopupWindow.setHorizontalOffset((int) (16 * density));
        mListPopupWindow.setVerticalOffset((int) (-48 * density));

        mListPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                AlbumsSpinner.this.onItemSelected(parent.getContext(), position);
                if (mOnItemSelectedListener != null) {
                    mOnItemSelectedListener.onItemSelected(parent, view, position, id);
                }
            }
        });
    }

    public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
        mOnItemSelectedListener = listener;
    }

    public void setSelection(Context context, int position) {
        mListPopupWindow.setSelection(position);
        onItemSelected(context, position);
    }

    private void onItemSelected(Context context, int position) {
        mListPopupWindow.dismiss();
        Cursor cursor = mAdapter.getCursor();
        cursor.moveToPosition(position);
        Album album = Album.valueOf(cursor);
        String displayName = album.getDisplayName(context);
        if (mSelected.getVisibility() == View.VISIBLE) {
            mSelected.setText(displayName);
        } else {
            if (Platform.hasICS()) {
                mSelected.setAlpha(0.0f);
                mSelected.setVisibility(View.VISIBLE);
                mSelected.setText(displayName);
                mSelected.animate().alpha(1.0f).setDuration(context.getResources().getInteger(
                        android.R.integer.config_longAnimTime)).start();
            } else {
                mSelected.setVisibility(View.VISIBLE);
                mSelected.setText(displayName);
            }

        }
    }

    public void setAdapter(CursorAdapter adapter) {
        mListPopupWindow.setAdapter(adapter);
        mAdapter = adapter;
    }

    public void setSelectedTextView(TextView textView) {
        mSelected = textView;
        // tint dropdown arrow icon
        Drawable[] drawables = mSelected.getCompoundDrawables();
        Drawable right = drawables[2];
        TypedArray ta = mSelected.getContext().getTheme().obtainStyledAttributes(
                new int[]{R.attr.album_element_color});
        int color = ta.getColor(0, 0);
        ta.recycle();
        right.setColorFilter(color, PorterDuff.Mode.SRC_IN);

        mSelected.setVisibility(View.GONE);
        mSelected.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                int itemHeight = v.getResources().getDimensionPixelSize(R.dimen.album_item_height);
                mListPopupWindow.setHeight(
                        mAdapter.getCount() > MAX_SHOWN_COUNT ? itemHeight * MAX_SHOWN_COUNT
                                : itemHeight * mAdapter.getCount());
                mListPopupWindow.show();
            }
        });
        mSelected.setOnTouchListener(mListPopupWindow.createDragToOpenListener(mSelected));
    }

    public void setPopupAnchorView(View view) {
        mListPopupWindow.setAnchorView(view);
    }

}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java
================================================
package com.zhihu.matisse.internal.ui.widget;

import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import androidx.core.content.res.ResourcesCompat;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;

import com.zhihu.matisse.R;

public class CheckRadioView extends AppCompatImageView {

    private Drawable mDrawable;

    private int mSelectedColor;
    private int mUnSelectUdColor;

    public CheckRadioView(Context context) {
        super(context);
        init();
    }



    public CheckRadioView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mSelectedColor = ResourcesCompat.getColor(
                getResources(), R.color.zhihu_item_checkCircle_backgroundColor,
                getContext().getTheme());
        mUnSelectUdColor = ResourcesCompat.getColor(
                getResources(), R.color.zhihu_check_original_radio_disable,
                getContext().getTheme());
        setChecked(false);
    }

    public void setChecked(boolean enable) {
        if (enable) {
            setImageResource(R.drawable.ic_preview_radio_on);
            mDrawable = getDrawable();
            mDrawable.setColorFilter(mSelectedColor, PorterDuff.Mode.SRC_IN);
        } else {
            setImageResource(R.drawable.ic_preview_radio_off);
            mDrawable = getDrawable();
            mDrawable.setColorFilter(mUnSelectUdColor, PorterDuff.Mode.SRC_IN);
        }
    }


    public void setColor(int color) {
        if (mDrawable == null) {
            mDrawable = getDrawable();
        }
        mDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import androidx.core.content.res.ResourcesCompat;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;

import com.zhihu.matisse.R;

public class CheckView extends View {

    public static final int UNCHECKED = Integer.MIN_VALUE;
    private static final float STROKE_WIDTH = 3.0f; // dp
    private static final float SHADOW_WIDTH = 6.0f; // dp
    private static final int SIZE = 48; // dp
    private static final float STROKE_RADIUS = 11.5f; // dp
    private static final float BG_RADIUS = 11.0f; // dp
    private static final int CONTENT_SIZE = 16; // dp
    private boolean mCountable;
    private boolean mChecked;
    private int mCheckedNum;
    private Paint mStrokePaint;
    private Paint mBackgroundPaint;
    private TextPaint mTextPaint;
    private Paint mShadowPaint;
    private Drawable mCheckDrawable;
    private float mDensity;
    private Rect mCheckRect;
    private boolean mEnabled = true;

    public CheckView(Context context) {
        super(context);
        init(context);
    }

    public CheckView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public CheckView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // fixed size 48dp x 48dp
        int sizeSpec = MeasureSpec.makeMeasureSpec((int) (SIZE * mDensity), MeasureSpec.EXACTLY);
        super.onMeasure(sizeSpec, sizeSpec);
    }

    private void init(Context context) {
        mDensity = context.getResources().getDisplayMetrics().density;

        mStrokePaint = new Paint();
        mStrokePaint.setAntiAlias(true);
        mStrokePaint.setStyle(Paint.Style.STROKE);
        mStrokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
        mStrokePaint.setStrokeWidth(STROKE_WIDTH * mDensity);
        TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{R.attr.item_checkCircle_borderColor});
        int defaultColor = ResourcesCompat.getColor(
                getResources(), R.color.zhihu_item_checkCircle_borderColor,
                getContext().getTheme());
        int color = ta.getColor(0, defaultColor);
        ta.recycle();
        mStrokePaint.setColor(color);

        mCheckDrawable = ResourcesCompat.getDrawable(context.getResources(),
                R.drawable.ic_check_white_18dp, context.getTheme());
    }

    public void setChecked(boolean checked) {
        if (mCountable) {
            throw new IllegalStateException("CheckView is countable, call setCheckedNum() instead.");
        }
        mChecked = checked;
        invalidate();
    }

    public void setCountable(boolean countable) {
        mCountable = countable;
    }

    public void setCheckedNum(int checkedNum) {
        if (!mCountable) {
            throw new IllegalStateException("CheckView is not countable, call setChecked() instead.");
        }
        if (checkedNum != UNCHECKED && checkedNum <= 0) {
            throw new IllegalArgumentException("checked num can't be negative.");
        }
        mCheckedNum = checkedNum;
        invalidate();
    }

    public void setEnabled(boolean enabled) {
        if (mEnabled != enabled) {
            mEnabled = enabled;
            invalidate();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // draw outer and inner shadow
        initShadowPaint();
        canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
                (STROKE_RADIUS + STROKE_WIDTH / 2 + SHADOW_WIDTH) * mDensity, mShadowPaint);

        // draw white stroke
        canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
                STROKE_RADIUS * mDensity, mStrokePaint);

        // draw content
        if (mCountable) {
            if (mCheckedNum != UNCHECKED) {
                initBackgroundPaint();
                canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
                        BG_RADIUS * mDensity, mBackgroundPaint);
                initTextPaint();
                String text = String.valueOf(mCheckedNum);
                int baseX = (int) (canvas.getWidth() - mTextPaint.measureText(text)) / 2;
                int baseY = (int) (canvas.getHeight() - mTextPaint.descent() - mTextPaint.ascent()) / 2;
                canvas.drawText(text, baseX, baseY, mTextPaint);
            }
        } else {
            if (mChecked) {
                initBackgroundPaint();
                canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
                        BG_RADIUS * mDensity, mBackgroundPaint);

                mCheckDrawable.setBounds(getCheckRect());
                mCheckDrawable.draw(canvas);
            }
        }

        // enable hint
        setAlpha(mEnabled ? 1.0f : 0.5f);
    }

    private void initShadowPaint() {
        if (mShadowPaint == null) {
            mShadowPaint = new Paint();
            mShadowPaint.setAntiAlias(true);
            // all in dp
            float outerRadius = STROKE_RADIUS + STROKE_WIDTH / 2;
            float innerRadius = outerRadius - STROKE_WIDTH;
            float gradientRadius = outerRadius + SHADOW_WIDTH;
            float stop0 = (innerRadius - SHADOW_WIDTH) / gradientRadius;
            float stop1 = innerRadius / gradientRadius;
            float stop2 = outerRadius / gradientRadius;
            float stop3 = 1.0f;
            mShadowPaint.setShader(
                    new RadialGradient((float) SIZE * mDensity / 2,
                            (float) SIZE * mDensity / 2,
                            gradientRadius * mDensity,
                            new int[]{Color.parseColor("#00000000"), Color.parseColor("#0D000000"),
                                    Color.parseColor("#0D000000"), Color.parseColor("#00000000")},
                            new float[]{stop0, stop1, stop2, stop3},
                            Shader.TileMode.CLAMP));
        }
    }

    private void initBackgroundPaint() {
        if (mBackgroundPaint == null) {
            mBackgroundPaint = new Paint();
            mBackgroundPaint.setAntiAlias(true);
            mBackgroundPaint.setStyle(Paint.Style.FILL);
            TypedArray ta = getContext().getTheme()
                    .obtainStyledAttributes(new int[]{R.attr.item_checkCircle_backgroundColor});
            int defaultColor = ResourcesCompat.getColor(
                    getResources(), R.color.zhihu_item_checkCircle_backgroundColor,
                    getContext().getTheme());
            int color = ta.getColor(0, defaultColor);
            ta.recycle();
            mBackgroundPaint.setColor(color);
        }
    }

    private void initTextPaint() {
        if (mTextPaint == null) {
            mTextPaint = new TextPaint();
            mTextPaint.setAntiAlias(true);
            mTextPaint.setColor(Color.WHITE);
            mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
            mTextPaint.setTextSize(12.0f * mDensity);
        }
    }

    // rect for drawing checked number or mark
    private Rect getCheckRect() {
        if (mCheckRect == null) {
            int rectPadding = (int) (SIZE * mDensity / 2 - CONTENT_SIZE * mDensity / 2);
            mCheckRect = new Rect(rectPadding, rectPadding,
                    (int) (SIZE * mDensity - rectPadding), (int) (SIZE * mDensity - rectPadding));
        }

        return mCheckRect;
    }
}


================================================
FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java
================================================
/*
 * Copyright 2017 Zhihu Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zhihu.matisse.internal.ui.widget;

import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.appcompat.app.AlertDialog;
import android.text.TextUtils;

import com.zhihu.matisse.R;

public class IncapableDialog extends DialogFragment {

    public static final String EXTRA_TITLE = "extra_title";
    public static final String EXTRA_MESSAGE = "extra_message";

    public static IncapableDialog newInstance(String title, String message) {
        IncapableDialog dialog = new IncapableDialog();
        Bundle args = new Bundle();
        args.putString(EXTRA_TITLE, title);
        args.putString(EXTRA_MESSAGE, message);
        dialog.setArguments(args);
        return dialog;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        String title = getArguments().getString(EXTRA_TITLE);
        String message = getArguments().getString(EXTRA_MESSAGE);

        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        if (!TextUtils.isEmpty(title)) {
            bu
Download .txt
gitextract_8rhgqmr3/

├── .github/
│   └── ISSUE_TEMPLATE/
│       └── bug_report.md
├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── build.gradle
├── checkstyle.xml
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── matisse/
│   ├── build.gradle
│   ├── gradle.properties
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── zhihu/
│           │           └── matisse/
│           │               ├── Matisse.java
│           │               ├── MimeType.java
│           │               ├── SelectionCreator.java
│           │               ├── engine/
│           │               │   ├── ImageEngine.java
│           │               │   └── impl/
│           │               │       ├── GlideEngine.java
│           │               │       └── PicassoEngine.java
│           │               ├── filter/
│           │               │   └── Filter.java
│           │               ├── internal/
│           │               │   ├── entity/
│           │               │   │   ├── Album.java
│           │               │   │   ├── CaptureStrategy.java
│           │               │   │   ├── IncapableCause.java
│           │               │   │   ├── Item.java
│           │               │   │   └── SelectionSpec.java
│           │               │   ├── loader/
│           │               │   │   ├── AlbumLoader.java
│           │               │   │   └── AlbumMediaLoader.java
│           │               │   ├── model/
│           │               │   │   ├── AlbumCollection.java
│           │               │   │   ├── AlbumMediaCollection.java
│           │               │   │   └── SelectedItemCollection.java
│           │               │   ├── ui/
│           │               │   │   ├── AlbumPreviewActivity.java
│           │               │   │   ├── BasePreviewActivity.java
│           │               │   │   ├── MediaSelectionFragment.java
│           │               │   │   ├── PreviewItemFragment.java
│           │               │   │   ├── SelectedPreviewActivity.java
│           │               │   │   ├── adapter/
│           │               │   │   │   ├── AlbumMediaAdapter.java
│           │               │   │   │   ├── AlbumsAdapter.java
│           │               │   │   │   ├── PreviewPagerAdapter.java
│           │               │   │   │   └── RecyclerViewCursorAdapter.java
│           │               │   │   └── widget/
│           │               │   │       ├── AlbumsSpinner.java
│           │               │   │       ├── CheckRadioView.java
│           │               │   │       ├── CheckView.java
│           │               │   │       ├── IncapableDialog.java
│           │               │   │       ├── MediaGrid.java
│           │               │   │       ├── MediaGridInset.java
│           │               │   │       ├── PreviewViewPager.java
│           │               │   │       ├── RoundedRectangleImageView.java
│           │               │   │       └── SquareFrameLayout.java
│           │               │   └── utils/
│           │               │       ├── ExifInterfaceCompat.java
│           │               │       ├── MediaStoreCompat.java
│           │               │       ├── PathUtils.java
│           │               │       ├── PhotoMetadataUtils.java
│           │               │       ├── Platform.java
│           │               │       ├── SingleMediaScanner.java
│           │               │       └── UIUtils.java
│           │               ├── listener/
│           │               │   ├── OnCheckedListener.java
│           │               │   ├── OnFragmentInteractionListener.java
│           │               │   └── OnSelectedListener.java
│           │               └── ui/
│           │                   └── MatisseActivity.java
│           └── res/
│               ├── color/
│               │   ├── dracula_bottom_toolbar_apply.xml
│               │   ├── dracula_bottom_toolbar_preview.xml
│               │   ├── dracula_preview_bottom_toolbar_apply.xml
│               │   ├── zhihu_bottom_toolbar_apply.xml
│               │   ├── zhihu_bottom_toolbar_preview.xml
│               │   └── zhihu_preview_bottom_toolbar_apply.xml
│               ├── layout/
│               │   ├── activity_matisse.xml
│               │   ├── activity_media_preview.xml
│               │   ├── album_list_item.xml
│               │   ├── fragment_media_selection.xml
│               │   ├── fragment_preview_item.xml
│               │   ├── media_grid_content.xml
│               │   ├── media_grid_item.xml
│               │   └── photo_capture_item.xml
│               ├── values/
│               │   ├── attrs.xml
│               │   ├── colors.xml
│               │   ├── colors_dracula.xml
│               │   ├── colors_zhihu.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               ├── values-ar/
│               │   └── strings.xml
│               ├── values-ca/
│               │   └── strings.xml
│               ├── values-de/
│               │   └── strings.xml
│               ├── values-es/
│               │   └── strings.xml
│               ├── values-it/
│               │   └── strings.xml
│               ├── values-ko/
│               │   └── strings.xml
│               ├── values-pl/
│               │   └── strings.xml
│               ├── values-pt-rBR/
│               │   └── strings.xml
│               ├── values-ru/
│               │   └── strings.xml
│               ├── values-tr-rTR/
│               │   └── strings.xml
│               ├── values-uk/
│               │   └── strings.xml
│               ├── values-vi/
│               │   └── strings.xml
│               ├── values-zh/
│               │   └── strings.xml
│               └── values-zh-rTW/
│                   └── strings.xml
├── sample/
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── zhihu/
│           │           └── matisse/
│           │               └── sample/
│           │                   ├── GifSizeFilter.java
│           │                   └── SampleActivity.java
│           └── res/
│               ├── layout/
│               │   ├── activity_main.xml
│               │   └── uri_item.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               ├── values-ca/
│               │   └── strings.xml
│               ├── values-es/
│               │   └── strings.xml
│               ├── values-it/
│               │   └── strings.xml
│               ├── values-ko/
│               │   └── strings.xml
│               ├── values-pt-rBR/
│               │   └── strings.xml
│               ├── values-ru/
│               │   └── strings.xml
│               ├── values-tr-rTR/
│               │   └── strings.xml
│               ├── values-uk/
│               │   └── strings.xml
│               ├── values-zh/
│               │   └── strings.xml
│               ├── values-zh-rTW/
│               │   └── strings.xml
│               └── xml/
│                   ├── file_paths_private.xml
│                   └── file_paths_public.xml
└── settings.gradle
Download .txt
SYMBOL INDEX (438 symbols across 48 files)

FILE: matisse/src/main/java/com/zhihu/matisse/Matisse.java
  class Matisse (line 33) | public final class Matisse {
    method Matisse (line 38) | private Matisse(Activity activity) {
    method Matisse (line 42) | private Matisse(Fragment fragment) {
    method Matisse (line 46) | private Matisse(Activity activity, Fragment fragment) {
    method from (line 60) | public static Matisse from(Activity activity) {
    method from (line 73) | public static Matisse from(Fragment fragment) {
    method obtainResult (line 84) | public static List<Uri> obtainResult(Intent data) {
    method obtainPathResult (line 95) | public static List<String> obtainPathResult(Intent data) {
    method obtainOriginalState (line 106) | public static boolean obtainOriginalState(Intent data) {
    method choose (line 120) | public SelectionCreator choose(Set<MimeType> mimeTypes) {
    method choose (line 137) | public SelectionCreator choose(Set<MimeType> mimeTypes, boolean mediaT...
    method getActivity (line 141) | @Nullable
    method getFragment (line 146) | @Nullable

FILE: matisse/src/main/java/com/zhihu/matisse/MimeType.java
  type MimeType (line 41) | @SuppressWarnings("unused")
    method MimeType (line 98) | MimeType(String mimeTypeName, Set<String> extensions) {
    method ofAll (line 103) | public static Set<MimeType> ofAll() {
    method of (line 107) | public static Set<MimeType> of(MimeType type, MimeType... rest) {
    method ofImage (line 111) | public static Set<MimeType> ofImage() {
    method ofImage (line 115) | public static Set<MimeType> ofImage(boolean onlyGif) {
    method ofGif (line 119) | public static Set<MimeType> ofGif() {
    method ofVideo (line 123) | public static Set<MimeType> ofVideo() {
    method isImage (line 127) | public static boolean isImage(String mimeType) {
    method isVideo (line 132) | public static boolean isVideo(String mimeType) {
    method isGif (line 137) | public static boolean isGif(String mimeType) {
    method arraySetOf (line 142) | private static Set<String> arraySetOf(String... suffixes) {
    method toString (line 146) | @Override
    method checkType (line 151) | public boolean checkType(ContentResolver resolver, Uri uri) {

FILE: matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java
  class SelectionCreator (line 62) | @SuppressWarnings("unused")
    method SelectionCreator (line 96) | SelectionCreator(Matisse matisse, @NonNull Set<MimeType> mimeTypes, bo...
    method showSingleMediaType (line 112) | public SelectionCreator showSingleMediaType(boolean showSingleMediaTyp...
    method theme (line 128) | public SelectionCreator theme(@StyleRes int themeId) {
    method countable (line 140) | public SelectionCreator countable(boolean countable) {
    method maxSelectable (line 151) | public SelectionCreator maxSelectable(int maxSelectable) {
    method maxSelectablePerMediaType (line 168) | public SelectionCreator maxSelectablePerMediaType(int maxImageSelectab...
    method addFilter (line 183) | public SelectionCreator addFilter(@NonNull Filter filter) {
    method capture (line 200) | public SelectionCreator capture(boolean enable) {
    method originalEnable (line 211) | public SelectionCreator originalEnable(boolean enable) {
    method autoHideToolbarOnSingleTap (line 222) | public SelectionCreator autoHideToolbarOnSingleTap(boolean enable) {
    method maxOriginalSize (line 233) | public SelectionCreator maxOriginalSize(int size) {
    method captureStrategy (line 245) | public SelectionCreator captureStrategy(CaptureStrategy captureStrateg...
    method restrictOrientation (line 258) | public SelectionCreator restrictOrientation(@ScreenOrientation int ori...
    method spanCount (line 271) | public SelectionCreator spanCount(int spanCount) {
    method gridExpectedSize (line 285) | public SelectionCreator gridExpectedSize(int size) {
    method thumbnailScale (line 297) | public SelectionCreator thumbnailScale(float scale) {
    method imageEngine (line 315) | public SelectionCreator imageEngine(ImageEngine imageEngine) {
    method setOnSelectedListener (line 329) | @NonNull
    method setOnCheckedListener (line 341) | public SelectionCreator setOnCheckedListener(@Nullable OnCheckedListen...
    method forResult (line 351) | public void forResult(int requestCode) {
    method showPreview (line 367) | public SelectionCreator showPreview(boolean showPreview) {

FILE: matisse/src/main/java/com/zhihu/matisse/engine/ImageEngine.java
  type ImageEngine (line 27) | @SuppressWarnings("unused")
    method loadThumbnail (line 39) | void loadThumbnail(Context context, int resize, Drawable placeholder, ...
    method loadGifThumbnail (line 51) | void loadGifThumbnail(Context context, int resize, Drawable placeholde...
    method loadImage (line 62) | void loadImage(Context context, int resizeX, int resizeY, ImageView im...
    method loadGifImage (line 73) | void loadGifImage(Context context, int resizeX, int resizeY, ImageView...
    method supportAnimatedGif (line 81) | boolean supportAnimatedGif();

FILE: matisse/src/main/java/com/zhihu/matisse/engine/impl/GlideEngine.java
  class GlideEngine (line 32) | public class GlideEngine implements ImageEngine {
    method loadThumbnail (line 34) | @Override
    method loadGifThumbnail (line 46) | @Override
    method loadImage (line 59) | @Override
    method loadGifImage (line 70) | @Override
    method supportAnimatedGif (line 82) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/engine/impl/PicassoEngine.java
  class PicassoEngine (line 30) | public class PicassoEngine implements ImageEngine {
    method loadThumbnail (line 32) | @Override
    method loadGifThumbnail (line 40) | @Override
    method loadImage (line 46) | @Override
    method loadGifImage (line 52) | @Override
    method supportAnimatedGif (line 57) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/filter/Filter.java
  class Filter (line 31) | @SuppressWarnings("unused")
    method constraintTypes (line 49) | protected abstract Set<MimeType> constraintTypes();
    method filter (line 56) | public abstract IncapableCause filter(Context context, Item item);
    method needFiltering (line 61) | protected boolean needFiltering(Context context, Item item) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java
  class Album (line 30) | public class Album implements Parcelable {
    method createFromParcel (line 32) | @Nullable
    method newArray (line 38) | @Override
    method Album (line 51) | public Album(String id, Uri coverUri, String albumName, long count) {
    method Album (line 58) | private Album(Parcel source) {
    method valueOf (line 69) | public static Album valueOf(Cursor cursor) {
    method describeContents (line 78) | @Override
    method writeToParcel (line 83) | @Override
    method getId (line 91) | public String getId() {
    method getCoverUri (line 95) | public Uri getCoverUri() {
    method getCount (line 99) | public long getCount() {
    method addCaptureCount (line 103) | public void addCaptureCount() {
    method getDisplayName (line 107) | public String getDisplayName(Context context) {
    method isAll (line 114) | public boolean isAll() {
    method isEmpty (line 118) | public boolean isEmpty() {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/CaptureStrategy.java
  class CaptureStrategy (line 18) | public class CaptureStrategy {
    method CaptureStrategy (line 24) | public CaptureStrategy(boolean isPublic, String authority) {
    method CaptureStrategy (line 28) | public CaptureStrategy(boolean isPublic, String authority, String dire...

FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java
  class IncapableCause (line 29) | @SuppressWarnings("unused")
    method IncapableCause (line 44) | public IncapableCause(String message) {
    method IncapableCause (line 48) | public IncapableCause(String title, String message) {
    method IncapableCause (line 53) | public IncapableCause(@Form int form, String message) {
    method IncapableCause (line 58) | public IncapableCause(@Form int form, String title, String message) {
    method handleCause (line 64) | public static void handleCause(Context context, IncapableCause cause) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java
  class Item (line 29) | public class Item implements Parcelable {
    method createFromParcel (line 31) | @Override
    method newArray (line 37) | @Override
    method Item (line 50) | private Item(long id, String mimeType, long size, long duration) {
    method Item (line 67) | private Item(Parcel source) {
    method valueOf (line 75) | public static Item valueOf(Cursor cursor) {
    method describeContents (line 82) | @Override
    method writeToParcel (line 87) | @Override
    method getContentUri (line 96) | public Uri getContentUri() {
    method isCapture (line 100) | public boolean isCapture() {
    method isImage (line 104) | public boolean isImage() {
    method isGif (line 108) | public boolean isGif() {
    method isVideo (line 112) | public boolean isVideo() {
    method equals (line 116) | @Override
    method hashCode (line 132) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java
  class SelectionSpec (line 34) | public final class SelectionSpec {
    method SelectionSpec (line 61) | private SelectionSpec() {
    method getInstance (line 64) | public static SelectionSpec getInstance() {
    method getCleanInstance (line 68) | public static SelectionSpec getCleanInstance() {
    method reset (line 74) | private void reset() {
    method singleSelectionModeEnabled (line 98) | public boolean singleSelectionModeEnabled() {
    method needOrientationRestriction (line 102) | public boolean needOrientationRestriction() {
    method onlyShowImages (line 106) | public boolean onlyShowImages() {
    method onlyShowVideos (line 110) | public boolean onlyShowVideos() {
    method onlyShowGif (line 114) | public boolean onlyShowGif() {
    class InstanceHolder (line 118) | private static final class InstanceHolder {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java
  class AlbumLoader (line 42) | public class AlbumLoader extends CursorLoader {
    method getSelectionArgsForSingleMediaType (line 98) | private static String[] getSelectionArgsForSingleMediaType(int mediaTy...
    method getSelectionArgsForSingleMediaGifType (line 114) | private static String[] getSelectionArgsForSingleMediaGifType(int medi...
    method AlbumLoader (line 121) | private AlbumLoader(Context context, String selection, String[] select...
    method newInstance (line 132) | public static CursorLoader newInstance(Context context) {
    method loadInBackground (line 157) | @Override
    method getUri (line 263) | private static Uri getUri(Cursor cursor) {
    method onContentChanged (line 282) | @Override
    method beforeAndroidTen (line 290) | private static boolean beforeAndroidTen() {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java
  class AlbumMediaLoader (line 36) | public class AlbumMediaLoader extends CursorLoader {
    method getSelectionArgsForSingleMediaType (line 62) | private static String[] getSelectionArgsForSingleMediaType(int mediaTy...
    method getSelectionAlbumArgs (line 76) | private static String[] getSelectionAlbumArgs(String albumId) {
    method getSelectionAlbumArgsForSingleMediaType (line 92) | private static String[] getSelectionAlbumArgsForSingleMediaType(int me...
    method getSelectionArgsForGifType (line 104) | private static String[] getSelectionArgsForGifType(int mediaType) {
    method getSelectionAlbumArgsForGifType (line 118) | private static String[] getSelectionAlbumArgsForGifType(int mediaType,...
    method AlbumMediaLoader (line 126) | private AlbumMediaLoader(Context context, String selection, String[] s...
    method newInstance (line 131) | public static CursorLoader newInstance(Context context, Album album, b...
    method loadInBackground (line 182) | @Override
    method onContentChanged (line 193) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java
  class AlbumCollection (line 30) | public class AlbumCollection implements LoaderManager.LoaderCallbacks<Cu...
    method onCreateLoader (line 39) | @Override
    method onLoadFinished (line 49) | @Override
    method onLoaderReset (line 62) | @Override
    method onCreate (line 72) | public void onCreate(FragmentActivity activity, AlbumCallbacks callbac...
    method onRestoreInstanceState (line 78) | public void onRestoreInstanceState(Bundle savedInstanceState) {
    method onSaveInstanceState (line 86) | public void onSaveInstanceState(Bundle outState) {
    method onDestroy (line 90) | public void onDestroy() {
    method loadAlbums (line 97) | public void loadAlbums() {
    method getCurrentSelection (line 101) | public int getCurrentSelection() {
    method setStateCurrentSelection (line 105) | public void setStateCurrentSelection(int currentSelection) {
    type AlbumCallbacks (line 109) | public interface AlbumCallbacks {
      method onAlbumLoad (line 110) | void onAlbumLoad(Cursor cursor);
      method onAlbumReset (line 112) | void onAlbumReset();

FILE: matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java
  class AlbumMediaCollection (line 33) | public class AlbumMediaCollection implements LoaderManager.LoaderCallbac...
    method onCreateLoader (line 41) | @Override
    method onLoadFinished (line 57) | @Override
    method onLoaderReset (line 67) | @Override
    method onCreate (line 77) | public void onCreate(@NonNull FragmentActivity context, @NonNull Album...
    method onDestroy (line 83) | public void onDestroy() {
    method load (line 90) | public void load(@Nullable Album target) {
    method load (line 94) | public void load(@Nullable Album target, boolean enableCapture) {
    type AlbumMediaCallbacks (line 101) | public interface AlbumMediaCallbacks {
      method onAlbumMediaLoad (line 103) | void onAlbumMediaLoad(Cursor cursor);
      method onAlbumMediaReset (line 105) | void onAlbumMediaReset();

FILE: matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java
  class SelectedItemCollection (line 36) | @SuppressWarnings("unused")
    method SelectedItemCollection (line 61) | public SelectedItemCollection(Context context) {
    method onCreate (line 65) | public void onCreate(Bundle bundle) {
    method setDefaultSelection (line 75) | public void setDefaultSelection(List<Item> uris) {
    method onSaveInstanceState (line 79) | public void onSaveInstanceState(Bundle outState) {
    method getDataWithBundle (line 84) | public Bundle getDataWithBundle() {
    method add (line 91) | public boolean add(Item item) {
    method remove (line 116) | public boolean remove(Item item) {
    method overwrite (line 130) | public void overwrite(ArrayList<Item> items, int collectionType) {
    method asList (line 141) | public List<Item> asList() {
    method asListOfUri (line 145) | public List<Uri> asListOfUri() {
    method asListOfString (line 153) | public List<String> asListOfString() {
    method isEmpty (line 161) | public boolean isEmpty() {
    method isSelected (line 165) | public boolean isSelected(Item item) {
    method isAcceptable (line 169) | public IncapableCause isAcceptable(Item item) {
    method maxSelectableReached (line 200) | public boolean maxSelectableReached() {
    method currentMaxSelectable (line 205) | private int currentMaxSelectable() {
    method getCollectionType (line 218) | public int getCollectionType() {
    method refineCollectionType (line 222) | private void refineCollectionType() {
    method typeConflict (line 242) | public boolean typeConflict(Item item) {
    method count (line 248) | public int count() {
    method checkedNumOf (line 252) | public int checkedNumOf(Item item) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java
  class AlbumPreviewActivity (line 31) | public class AlbumPreviewActivity extends BasePreviewActivity implements
    method onCreate (line 41) | @Override
    method onDestroy (line 62) | @Override
    method onAlbumMediaLoad (line 68) | @Override
    method onAlbumMediaReset (line 93) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java
  class BasePreviewActivity (line 45) | public abstract class BasePreviewActivity extends AppCompatActivity impl...
    method onCreate (line 75) | @Override
    method onSaveInstanceState (line 179) | @Override
    method onBackPressed (line 186) | @Override
    method onClick (line 192) | @Override
    method onClick (line 202) | @Override
    method onPageScrolled (line 232) | @Override
    method onPageSelected (line 237) | @Override
    method onPageScrollStateChanged (line 266) | @Override
    method updateApplyButton (line 271) | private void updateApplyButton() {
    method updateOriginalState (line 293) | private void updateOriginalState() {
    method countOverMaxSize (line 315) | private int countOverMaxSize() {
    method updateSize (line 330) | protected void updateSize(Item item) {
    method sendBackResult (line 345) | protected void sendBackResult(boolean apply) {
    method assertAddSelection (line 353) | private boolean assertAddSelection(Item item) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java
  class MediaSelectionFragment (line 39) | public class MediaSelectionFragment extends Fragment implements
    method newInstance (line 52) | public static MediaSelectionFragment newInstance(Album album) {
    method onAttach (line 60) | @Override
    method onCreateView (line 76) | @Nullable
    method onViewCreated (line 83) | @Override
    method onActivityCreated (line 89) | @Override
    method onDestroyView (line 116) | @Override
    method refreshMediaGrid (line 122) | public void refreshMediaGrid() {
    method refreshSelection (line 126) | public void refreshSelection() {
    method onAlbumMediaLoad (line 130) | @Override
    method onAlbumMediaReset (line 135) | @Override
    method onUpdate (line 140) | @Override
    method onMediaClick (line 148) | @Override
    type SelectionProvider (line 156) | public interface SelectionProvider {
      method provideSelectedItemCollection (line 157) | SelectedItemCollection provideSelectedItemCollection();

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java
  class PreviewItemFragment (line 39) | public class PreviewItemFragment extends Fragment {
    method newInstance (line 44) | public static PreviewItemFragment newInstance(Item item) {
    method onCreateView (line 52) | @Override
    method onViewCreated (line 57) | @Override
    method resetView (line 106) | public void resetView() {
    method onAttach (line 113) | @Override
    method onDetach (line 124) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java
  class SelectedPreviewActivity (line 27) | public class SelectedPreviewActivity extends BasePreviewActivity {
    method onCreate (line 29) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java
  class AlbumMediaAdapter (line 40) | public class AlbumMediaAdapter extends
    method AlbumMediaAdapter (line 54) | public AlbumMediaAdapter(Context context, SelectedItemCollection selec...
    method onCreateViewHolder (line 66) | @Override
    method onBindViewHolder (line 87) | @Override
    method setCheckStatus (line 128) | private void setCheckStatus(Item item, MediaGrid mediaGrid) {
    method onThumbnailClicked (line 160) | @Override
    method onCheckViewClicked (line 171) | @Override
    method updateSelectedItem (line 176) | private void updateSelectedItem(Item item, RecyclerView.ViewHolder hol...
    method notifyCheckStateChanged (line 201) | private void notifyCheckStateChanged() {
    method getItemViewType (line 208) | @Override
    method assertAddSelection (line 213) | private boolean assertAddSelection(Context context, Item item) {
    method registerCheckStateListener (line 220) | public void registerCheckStateListener(CheckStateListener listener) {
    method unregisterCheckStateListener (line 224) | public void unregisterCheckStateListener() {
    method registerOnMediaClickListener (line 228) | public void registerOnMediaClickListener(OnMediaClickListener listener) {
    method unregisterOnMediaClickListener (line 232) | public void unregisterOnMediaClickListener() {
    method refreshSelection (line 236) | public void refreshSelection() {
    method getImageResize (line 254) | private int getImageResize(Context context) {
    type CheckStateListener (line 267) | public interface CheckStateListener {
      method onUpdate (line 268) | void onUpdate();
    type OnMediaClickListener (line 271) | public interface OnMediaClickListener {
      method onMediaClick (line 272) | void onMediaClick(Album album, Item item, int adapterPosition);
    type OnPhotoCapture (line 275) | public interface OnPhotoCapture {
      method capture (line 276) | void capture();
    class MediaViewHolder (line 279) | private static class MediaViewHolder extends RecyclerView.ViewHolder {
      method MediaViewHolder (line 283) | MediaViewHolder(View itemView) {
    class CaptureViewHolder (line 289) | private static class CaptureViewHolder extends RecyclerView.ViewHolder {
      method CaptureViewHolder (line 293) | CaptureViewHolder(View itemView) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumsAdapter.java
  class AlbumsAdapter (line 33) | public class AlbumsAdapter extends CursorAdapter {
    method AlbumsAdapter (line 37) | public AlbumsAdapter(Context context, Cursor c, boolean autoRequery) {
    method AlbumsAdapter (line 46) | public AlbumsAdapter(Context context, Cursor c, int flags) {
    method newView (line 55) | @Override
    method bindView (line 60) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java
  class PreviewPagerAdapter (line 29) | public class PreviewPagerAdapter extends FragmentPagerAdapter {
    method PreviewPagerAdapter (line 34) | public PreviewPagerAdapter(FragmentManager manager, OnPrimaryItemSetLi...
    method getItem (line 39) | @Override
    method getCount (line 44) | @Override
    method setPrimaryItem (line 49) | @Override
    method getMediaItem (line 57) | public Item getMediaItem(int position) {
    method addAll (line 61) | public void addAll(List<Item> items) {
    type OnPrimaryItemSetListener (line 65) | interface OnPrimaryItemSetListener {
      method onPrimaryItemSet (line 67) | void onPrimaryItemSet(int position);

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java
  class RecyclerViewCursorAdapter (line 22) | public abstract class RecyclerViewCursorAdapter<VH extends RecyclerView....
    method RecyclerViewCursorAdapter (line 28) | RecyclerViewCursorAdapter(Cursor c) {
    method onBindViewHolder (line 33) | protected abstract void onBindViewHolder(VH holder, Cursor cursor);
    method onBindViewHolder (line 35) | @Override
    method getItemViewType (line 48) | @Override
    method getItemViewType (line 57) | protected abstract int getItemViewType(int position, Cursor cursor);
    method getItemCount (line 59) | @Override
    method getItemId (line 68) | @Override
    method swapCursor (line 81) | public void swapCursor(Cursor newCursor) {
    method getCursor (line 98) | public Cursor getCursor() {
    method isDataValid (line 102) | private boolean isDataValid(Cursor cursor) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java
  class AlbumsSpinner (line 34) | public class AlbumsSpinner {
    method AlbumsSpinner (line 42) | public AlbumsSpinner(@NonNull Context context) {
    method setOnItemSelectedListener (line 62) | public void setOnItemSelectedListener(AdapterView.OnItemSelectedListen...
    method setSelection (line 66) | public void setSelection(Context context, int position) {
    method onItemSelected (line 71) | private void onItemSelected(Context context, int position) {
    method setAdapter (line 94) | public void setAdapter(CursorAdapter adapter) {
    method setSelectedTextView (line 99) | public void setSelectedTextView(TextView textView) {
    method setPopupAnchorView (line 125) | public void setPopupAnchorView(View view) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java
  class CheckRadioView (line 12) | public class CheckRadioView extends AppCompatImageView {
    method CheckRadioView (line 19) | public CheckRadioView(Context context) {
    method CheckRadioView (line 26) | public CheckRadioView(Context context, AttributeSet attrs) {
    method init (line 31) | private void init() {
    method setChecked (line 41) | public void setChecked(boolean enable) {
    method setColor (line 54) | public void setColor(int color) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java
  class CheckView (line 37) | public class CheckView extends View {
    method CheckView (line 58) | public CheckView(Context context) {
    method CheckView (line 63) | public CheckView(Context context, AttributeSet attrs) {
    method CheckView (line 68) | public CheckView(Context context, AttributeSet attrs, int defStyleAttr) {
    method onMeasure (line 73) | @Override
    method init (line 80) | private void init(Context context) {
    method setChecked (line 100) | public void setChecked(boolean checked) {
    method setCountable (line 108) | public void setCountable(boolean countable) {
    method setCheckedNum (line 112) | public void setCheckedNum(int checkedNum) {
    method setEnabled (line 123) | public void setEnabled(boolean enabled) {
    method onDraw (line 130) | @Override
    method initShadowPaint (line 170) | private void initShadowPaint() {
    method initBackgroundPaint (line 193) | private void initBackgroundPaint() {
    method initTextPaint (line 209) | private void initTextPaint() {
    method getCheckRect (line 220) | private Rect getCheckRect() {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java
  class IncapableDialog (line 28) | public class IncapableDialog extends DialogFragment {
    method newInstance (line 33) | public static IncapableDialog newInstance(String title, String message) {
    method onCreateDialog (line 42) | @NonNull

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java
  class MediaGrid (line 32) | public class MediaGrid extends SquareFrameLayout implements View.OnClick...
    method MediaGrid (line 43) | public MediaGrid(Context context) {
    method MediaGrid (line 48) | public MediaGrid(Context context, AttributeSet attrs) {
    method init (line 53) | private void init(Context context) {
    method onClick (line 65) | @Override
    method preBindMedia (line 76) | public void preBindMedia(PreBindInfo info) {
    method bindMedia (line 80) | public void bindMedia(Item item) {
    method getMedia (line 88) | public Item getMedia() {
    method setGifTag (line 92) | private void setGifTag() {
    method initCheckView (line 96) | private void initCheckView() {
    method setCheckEnabled (line 100) | public void setCheckEnabled(boolean enabled) {
    method setCheckedNum (line 104) | public void setCheckedNum(int checkedNum) {
    method setChecked (line 108) | public void setChecked(boolean checked) {
    method setImage (line 112) | private void setImage() {
    method setVideoDuration (line 122) | private void setVideoDuration() {
    method setOnMediaGridClickListener (line 131) | public void setOnMediaGridClickListener(OnMediaGridClickListener liste...
    method removeOnMediaGridClickListener (line 135) | public void removeOnMediaGridClickListener() {
    type OnMediaGridClickListener (line 139) | public interface OnMediaGridClickListener {
      method onThumbnailClicked (line 141) | void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView...
      method onCheckViewClicked (line 143) | void onCheckViewClicked(CheckView checkView, Item item, RecyclerView...
    class PreBindInfo (line 146) | public static class PreBindInfo {
      method PreBindInfo (line 152) | public PreBindInfo(int resize, Drawable placeholder, boolean checkVi...

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java
  class MediaGridInset (line 22) | public class MediaGridInset extends RecyclerView.ItemDecoration {
    method MediaGridInset (line 28) | public MediaGridInset(int spanCount, int spacing, boolean includeEdge) {
    method getItemOffsets (line 34) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java
  class PreviewViewPager (line 25) | public class PreviewViewPager extends ViewPager {
    method PreviewViewPager (line 27) | public PreviewViewPager(Context context, AttributeSet attrs) {
    method canScroll (line 31) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java
  class RoundedRectangleImageView (line 25) | public class RoundedRectangleImageView extends AppCompatImageView {
    method RoundedRectangleImageView (line 31) | public RoundedRectangleImageView(Context context) {
    method RoundedRectangleImageView (line 36) | public RoundedRectangleImageView(Context context, AttributeSet attrs) {
    method RoundedRectangleImageView (line 41) | public RoundedRectangleImageView(Context context, AttributeSet attrs, ...
    method init (line 46) | private void init(Context context) {
    method onMeasure (line 53) | @Override
    method onDraw (line 60) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareFrameLayout.java
  class SquareFrameLayout (line 22) | public class SquareFrameLayout extends FrameLayout {
    method SquareFrameLayout (line 24) | public SquareFrameLayout(Context context) {
    method SquareFrameLayout (line 28) | public SquareFrameLayout(Context context, AttributeSet attrs) {
    method onMeasure (line 32) | @Override

FILE: matisse/src/main/java/com/zhihu/matisse/internal/utils/ExifInterfaceCompat.java
  class ExifInterfaceCompat (line 31) | final class ExifInterfaceCompat {
    method ExifInterfaceCompat (line 38) | private ExifInterfaceCompat() {
    method newInstance (line 52) | public static ExifInterface newInstance(String filename) throws IOExce...
    method getExifDateTime (line 57) | private static Date getExifDateTime(String filepath) {
    method getExifDateTimeInMillis (line 89) | public static long getExifDateTimeInMillis(String filepath) {
    method getExifOrientation (line 103) | public static int getExifOrientation(String filepath) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java
  class MediaStoreCompat (line 41) | public class MediaStoreCompat {
    method MediaStoreCompat (line 49) | public MediaStoreCompat(Activity activity) {
    method MediaStoreCompat (line 54) | public MediaStoreCompat(Activity activity, Fragment fragment) {
    method hasCameraFeature (line 65) | public static boolean hasCameraFeature(Context context) {
    method setCaptureStrategy (line 70) | public void setCaptureStrategy(CaptureStrategy strategy) {
    method dispatchCaptureIntent (line 74) | public void dispatchCaptureIntent(Context context, int requestCode) {
    method createImageFile (line 108) | @SuppressWarnings("ResultOfMethodCallIgnored")
    method getCurrentPhotoUri (line 138) | public Uri getCurrentPhotoUri() {
    method getCurrentPhotoPath (line 142) | public String getCurrentPhotoPath() {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/utils/PathUtils.java
  class PathUtils (line 17) | public class PathUtils {
    method getPath (line 27) | @TargetApi(Build.VERSION_CODES.KITKAT)
    method getDataColumn (line 89) | public static String getDataColumn(Context context, Uri uri, String se...
    method isExternalStorageDocument (line 116) | public static boolean isExternalStorageDocument(Uri uri) {
    method isDownloadsDocument (line 124) | public static boolean isDownloadsDocument(Uri uri) {
    method isMediaDocument (line 132) | public static boolean isMediaDocument(Uri uri) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/utils/PhotoMetadataUtils.java
  class PhotoMetadataUtils (line 45) | public final class PhotoMetadataUtils {
    method PhotoMetadataUtils (line 50) | private PhotoMetadataUtils() {
    method getPixelsCount (line 54) | public static int getPixelsCount(ContentResolver resolver, Uri uri) {
    method getBitmapSize (line 59) | public static Point getBitmapSize(Uri uri, Activity activity) {
    method getBitmapBound (line 81) | public static Point getBitmapBound(ContentResolver resolver, Uri uri) {
    method getPath (line 104) | public static String getPath(ContentResolver resolver, Uri uri) {
    method isAcceptable (line 127) | public static IncapableCause isAcceptable(Context context, Item item) {
    method isSelectableType (line 143) | private static boolean isSelectableType(Context context, Item item) {
    method shouldRotate (line 157) | private static boolean shouldRotate(ContentResolver resolver, Uri uri) {
    method getSizeInMB (line 170) | public static float getSizeInMB(long sizeInBytes) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/utils/Platform.java
  class Platform (line 8) | public class Platform {
    method hasICS (line 9) | public static boolean hasICS() {
    method hasKitKat (line 13) | public static boolean hasKitKat() {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/utils/SingleMediaScanner.java
  class SingleMediaScanner (line 13) | public class SingleMediaScanner implements MediaScannerConnection.MediaS...
    type ScanListener (line 19) | public interface ScanListener {
      method onScanFinish (line 24) | void onScanFinish();
    method SingleMediaScanner (line 27) | public SingleMediaScanner(Context context, String mPath, ScanListener ...
    method onMediaScannerConnected (line 34) | @Override public void onMediaScannerConnected() {
    method onScanCompleted (line 38) | @Override public void onScanCompleted(String mPath, Uri mUri) {

FILE: matisse/src/main/java/com/zhihu/matisse/internal/utils/UIUtils.java
  class UIUtils (line 20) | public class UIUtils {
    method spanCount (line 22) | public static int spanCount(Context context, int gridExpectedSize) {

FILE: matisse/src/main/java/com/zhihu/matisse/listener/OnCheckedListener.java
  type OnCheckedListener (line 7) | public interface OnCheckedListener {
    method onCheck (line 8) | void onCheck(boolean isChecked);

FILE: matisse/src/main/java/com/zhihu/matisse/listener/OnFragmentInteractionListener.java
  type OnFragmentInteractionListener (line 6) | public interface OnFragmentInteractionListener {
    method onClick (line 10) | void onClick();

FILE: matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java
  type OnSelectedListener (line 24) | public interface OnSelectedListener {
    method onSelected (line 29) | void onSelected(@NonNull List<Uri> uriList, @NonNull List<String> path...

FILE: matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java
  class MatisseActivity (line 68) | public class MatisseActivity extends AppCompatActivity implements
    method onCreate (line 96) | @Override
    method onSaveInstanceState (line 158) | @Override
    method onDestroy (line 166) | @Override
    method onOptionsItemSelected (line 174) | @Override
    method onBackPressed (line 183) | @Override
    method onActivityResult (line 189) | @Override
    method updateBottomToolbar (line 250) | private void updateBottomToolbar() {
    method updateOriginalState (line 279) | private void updateOriginalState() {
    method countOverMaxSize (line 296) | private int countOverMaxSize() {
    method onClick (line 312) | @Override
    method onItemSelected (line 347) | @Override
    method onNothingSelected (line 358) | @Override
    method onAlbumLoad (line 363) | @Override
    method onAlbumReset (line 384) | @Override
    method onAlbumSelected (line 389) | private void onAlbumSelected(Album album) {
    method onUpdate (line 404) | @Override
    method onMediaClick (line 415) | @Override
    method provideSelectedItemCollection (line 425) | @Override
    method capture (line 430) | @Override

FILE: sample/src/main/java/com/zhihu/matisse/sample/GifSizeFilter.java
  class GifSizeFilter (line 30) | class GifSizeFilter extends Filter {
    method GifSizeFilter (line 36) | GifSizeFilter(int minWidth, int minHeight, int maxSizeInBytes) {
    method constraintTypes (line 42) | @Override
    method filter (line 49) | @Override

FILE: sample/src/main/java/com/zhihu/matisse/sample/SampleActivity.java
  class SampleActivity (line 45) | public class SampleActivity extends AppCompatActivity implements View.On...
    method onCreate (line 51) | @Override
    method onClick (line 65) | @SuppressLint("CheckResult")
    method startAction (line 81) | private void startAction(View v) {
    method onActivityResult (line 144) | @Override
    class UriAdapter (line 153) | private static class UriAdapter extends RecyclerView.Adapter<UriAdapte...
      method setData (line 158) | void setData(List<Uri> uris, List<String> paths) {
      method onCreateViewHolder (line 164) | @Override
      method onBindViewHolder (line 170) | @Override
      method getItemCount (line 179) | @Override
      class UriViewHolder (line 184) | static class UriViewHolder extends RecyclerView.ViewHolder {
        method UriViewHolder (line 189) | UriViewHolder(View contentView) {
Condensed preview — 123 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (365K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 630,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise descriptio"
  },
  {
    "path": ".gitignore",
    "chars": 543,
    "preview": "*.iml\n.gradle\n/local.properties\n.DS_Store\n/build\n/captures\n\n.idea/\n\n# .gitignore template from https://github.com/github"
  },
  {
    "path": ".travis.yml",
    "chars": 604,
    "preview": "language: android\n\nandroid:\n  components:\n    - tools\n    - platform-tools\n    - build-tools-28.0.3\n    - android-28\n   "
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3216,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 698,
    "preview": "# Matisse is an Open Source Project\n\n## You Should Know\n\n- To contribute with a small fix, simply create a pull request."
  },
  {
    "path": "LICENSE.txt",
    "chars": 11341,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 4136,
    "preview": "![Image](/image/banner.png)\n\n# Matisse\n[![Build Status](https://travis-ci.org/zhihu/Matisse.svg)](https://travis-ci.org/"
  },
  {
    "path": "build.gradle",
    "chars": 1046,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "checkstyle.xml",
    "chars": 6422,
    "preview": "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC\n    \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n    \"https://che"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 232,
    "preview": "#Thu Aug 22 11:37:43 CST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 783,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 4971,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2404,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "matisse/build.gradle",
    "chars": 2399,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/gradle.properties",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "matisse/proguard-rules.pro",
    "chars": 700,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /L"
  },
  {
    "path": "matisse/src/main/AndroidManifest.xml",
    "chars": 1179,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/Matisse.java",
    "chars": 5109,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/MimeType.java",
    "chars": 4937,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java",
    "chars": 14349,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/engine/ImageEngine.java",
    "chars": 2981,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/engine/impl/GlideEngine.java",
    "chars": 2924,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/engine/impl/PicassoEngine.java",
    "chars": 2049,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/filter/Filter.java",
    "chars": 2054,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java",
    "chars": 3497,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/entity/CaptureStrategy.java",
    "chars": 1087,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java",
    "chars": 2502,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java",
    "chars": 4503,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java",
    "chars": 3750,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java",
    "chars": 12140,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java",
    "chars": 8799,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java",
    "chars": 3370,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java",
    "chars": 3344,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java",
    "chars": 8371,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java",
    "chars": 3145,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java",
    "chars": 13220,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java",
    "chars": 5921,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java",
    "chars": 4695,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java",
    "chars": 1694,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java",
    "chars": 11444,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumsAdapter.java",
    "chars": 2675,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java",
    "chars": 2028,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java",
    "chars": 3349,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java",
    "chars": 4969,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java",
    "chars": 1756,
    "preview": "package com.zhihu.matisse.internal.ui.widget;\n\nimport android.content.Context;\nimport android.graphics.PorterDuff;\nimpor"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java",
    "chars": 8715,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java",
    "chars": 2192,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java",
    "chars": 5047,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java",
    "chars": 2237,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java",
    "chars": 1301,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java",
    "chars": 2116,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareFrameLayout.java",
    "chars": 1142,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/utils/ExifInterfaceCompat.java",
    "chars": 4604,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java",
    "chars": 5651,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/utils/PathUtils.java",
    "chars": 5076,
    "preview": "package com.zhihu.matisse.internal.utils;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentUris;\nimpo"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/utils/PhotoMetadataUtils.java",
    "chars": 6567,
    "preview": "/*\n * Copyright (C) 2014 nohana, Inc.\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 "
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/utils/Platform.java",
    "chars": 366,
    "preview": "package com.zhihu.matisse.internal.utils;\n\nimport android.os.Build;\n\n/**\n * @author JoongWon Baik\n */\npublic class Platf"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/utils/SingleMediaScanner.java",
    "chars": 1100,
    "preview": "package com.zhihu.matisse.internal.utils;\n\nimport android.content.Context;\nimport android.media.MediaScannerConnection;\n"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/internal/utils/UIUtils.java",
    "chars": 1071,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/listener/OnCheckedListener.java",
    "chars": 211,
    "preview": "package com.zhihu.matisse.listener;\n\n\n/**\n *  when original is enabled , callback immediately when user check or uncheck"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/listener/OnFragmentInteractionListener.java",
    "chars": 242,
    "preview": "package com.zhihu.matisse.listener;\n\n/**\n *  PreViewItemFragment 和  BasePreViewActivity 通信的接口 ,为了方便拿到 ImageViewTouch 的点击"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java",
    "chars": 970,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java",
    "chars": 17951,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "matisse/src/main/res/color/dracula_bottom_toolbar_apply.xml",
    "chars": 884,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/color/dracula_bottom_toolbar_preview.xml",
    "chars": 888,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/color/dracula_preview_bottom_toolbar_apply.xml",
    "chars": 900,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/color/zhihu_bottom_toolbar_apply.xml",
    "chars": 880,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/color/zhihu_bottom_toolbar_preview.xml",
    "chars": 884,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/color/zhihu_preview_bottom_toolbar_apply.xml",
    "chars": 897,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/layout/activity_matisse.xml",
    "chars": 5259,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "matisse/src/main/res/layout/activity_media_preview.xml",
    "chars": 4865,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/layout/album_list_item.xml",
    "chars": 2229,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/layout/fragment_media_selection.xml",
    "chars": 1219,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/layout/fragment_preview_item.xml",
    "chars": 1249,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/layout/media_grid_content.xml",
    "chars": 1795,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/layout/media_grid_item.xml",
    "chars": 861,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/layout/photo_capture_item.xml",
    "chars": 1414,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values/attrs.xml",
    "chars": 1847,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values/colors.xml",
    "chars": 761,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values/colors_dracula.xml",
    "chars": 1988,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values/colors_zhihu.xml",
    "chars": 2016,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values/dimens.xml",
    "chars": 787,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values/strings.xml",
    "chars": 2047,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values/styles.xml",
    "chars": 4998,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-ar/strings.xml",
    "chars": 1045,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"album_name_all\">جميع وسائل الإعلام</string>\n\n    <s"
  },
  {
    "path": "matisse/src/main/res/values-ca/strings.xml",
    "chars": 1687,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-de/strings.xml",
    "chars": 1643,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n  Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "matisse/src/main/res/values-es/strings.xml",
    "chars": 1708,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-it/strings.xml",
    "chars": 1680,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-ko/strings.xml",
    "chars": 892,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"album_name_all\">전체보기</string>\n  <string name=\"button_"
  },
  {
    "path": "matisse/src/main/res/values-pl/strings.xml",
    "chars": 1798,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-pt-rBR/strings.xml",
    "chars": 1711,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-ru/strings.xml",
    "chars": 2063,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-tr-rTR/strings.xml",
    "chars": 1634,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-uk/strings.xml",
    "chars": 1887,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-vi/strings.xml",
    "chars": 1407,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"error_over_original_size\">Không thể chọn hình ảnh l"
  },
  {
    "path": "matisse/src/main/res/values-zh/strings.xml",
    "chars": 1751,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "matisse/src/main/res/values-zh-rTW/strings.xml",
    "chars": 1583,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/build.gradle",
    "chars": 1571,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "sample/proguard-rules.pro",
    "chars": 699,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /L"
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "chars": 1742,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/java/com/zhihu/matisse/sample/GifSizeFilter.java",
    "chars": 1992,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "sample/src/main/java/com/zhihu/matisse/sample/SampleActivity.java",
    "chars": 7981,
    "preview": "/*\n * Copyright 2017 Zhihu Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
  },
  {
    "path": "sample/src/main/res/layout/activity_main.xml",
    "chars": 3544,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "sample/src/main/res/layout/uri_item.xml",
    "chars": 1968,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values/colors.xml",
    "chars": 684,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values/dimens.xml",
    "chars": 798,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "chars": 892,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values/styles.xml",
    "chars": 1103,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-ca/strings.xml",
    "chars": 879,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-es/strings.xml",
    "chars": 879,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-it/strings.xml",
    "chars": 926,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-ko/strings.xml",
    "chars": 254,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"app_name\">Matisse</string>\n  <string name=\"permission"
  },
  {
    "path": "sample/src/main/res/values-pt-rBR/strings.xml",
    "chars": 863,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-ru/strings.xml",
    "chars": 865,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-tr-rTR/strings.xml",
    "chars": 900,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-uk/strings.xml",
    "chars": 858,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-zh/strings.xml",
    "chars": 825,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/values-zh-rTW/strings.xml",
    "chars": 768,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/xml/file_paths_private.xml",
    "chars": 747,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "sample/src/main/res/xml/file_paths_public.xml",
    "chars": 703,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Zhihu Inc.\n\n  Licensed under the Apache License, Version 2."
  },
  {
    "path": "settings.gradle",
    "chars": 30,
    "preview": "include ':sample', ':matisse'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the zhihu/Matisse GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 123 files (334.1 KB), approximately 79.3k tokens, and a symbol index with 438 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!