Full Code of SharryChoo/SAlbum for AI

release c41c1102996d cached
228 files
709.2 KB
174.9k tokens
1665 symbols
1 requests
Download .txt
Showing preview only (808K chars total). Download the full file or copy to clipboard to get everything.
Repository: SharryChoo/SAlbum
Branch: release
Commit: c41c1102996d
Files: 228
Total size: 709.2 KB

Directory structure:
gitextract_4ugyik6s/

├── .gitignore
├── README.md
├── SharryKey
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   ├── release/
│   │   └── output.json
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── sharry/
│           │           └── app/
│           │               └── salbum/
│           │                   ├── MainActivity.kt
│           │                   └── WatermarkPreviewerRenderer.java
│           └── res/
│               ├── drawable/
│               │   ├── app_activity_main_launcher.xml
│               │   └── ic_launcher_foreground.xml
│               ├── layout/
│               │   └── app_activity_main.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   └── ic_launcher_round.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── ic_launcher_background.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               ├── values-zh/
│               │   └── strings.xml
│               └── xml/
│                   └── provider_paths.xml
├── assert/
│   └── SAlbum-1.0.1.apk
├── build.gradle
├── git
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── lib-album/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── base/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── ILoaderEngine.java
│           │                   ├── Loader.java
│           │                   └── MediaMeta.java
│           ├── copper/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── CropperCallback.java
│           │                   ├── CropperCallbackLambda.java
│           │                   ├── CropperConfig.java
│           │                   ├── CropperFragment.java
│           │                   └── CropperManager.java
│           ├── picker/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── FolderAdapter.java
│           │                   ├── FolderModel.java
│           │                   ├── PickerActivity.java
│           │                   ├── PickerAdapter.java
│           │                   ├── PickerCallback.java
│           │                   ├── PickerCallbackLambda.java
│           │                   ├── PickerConfig.java
│           │                   ├── PickerContract.java
│           │                   ├── PickerManager.java
│           │                   ├── PickerModel.java
│           │                   ├── PickerPresenter.java
│           │                   └── res/
│           │                       ├── drawable/
│           │                       │   ├── ic_album_picker_bottom_indicator.xml
│           │                       │   ├── ic_album_picker_camera_header.xml
│           │                       │   ├── ic_album_picker_fab.xml
│           │                       │   ├── ic_album_picker_gif.xml
│           │                       │   ├── ic_album_picker_right_arrow.xml
│           │                       │   ├── ic_album_picker_video_default.xml
│           │                       │   └── ic_album_picker_video_play.xml
│           │                       ├── layout/
│           │                       │   ├── lib_album_activity_picker.xml
│           │                       │   ├── lib_album_recycle_item_folder.xml
│           │                       │   ├── lib_album_recycle_item_header_camera.xml
│           │                       │   ├── lib_album_recycle_item_picture.xml
│           │                       │   └── lib_album_recycle_item_video.xml
│           │                       ├── values/
│           │                       │   ├── picker_colors.xml
│           │                       │   ├── picker_strings.xml
│           │                       │   └── picker_themes.xml
│           │                       └── values-zh/
│           │                           └── picker_strings.xml
│           ├── player/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── VideoPlayerActivity.java
│           │                   └── res/
│           │                       ├── drawable/
│           │                       │   ├── ic_album_player_video_pasue.xml
│           │                       │   └── ic_album_player_video_play.xml
│           │                       ├── layout/
│           │                       │   └── lib_album_activity_video_player.xml
│           │                       ├── layout-land/
│           │                       │   └── lib_album_activity_video_player.xml
│           │                       └── values/
│           │                           └── player_color.xml
│           ├── taker/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── AspectRatioFragment.java
│           │                   ├── ITakerContract.java
│           │                   ├── TakerActivity.java
│           │                   ├── TakerCallback.java
│           │                   ├── TakerCallbackLambda.java
│           │                   ├── TakerConfig.java
│           │                   ├── TakerManager.java
│           │                   ├── TakerPresenter.java
│           │                   └── res/
│           │                       ├── drawable/
│           │                       │   ├── ic_album_taker_aspect.xml
│           │                       │   ├── ic_album_taker_camera_switch.xml
│           │                       │   ├── ic_album_taker_denied.xml
│           │                       │   ├── ic_album_taker_full_screen.xml
│           │                       │   └── ic_album_taker_granted.xml
│           │                       ├── layout/
│           │                       │   └── lib_ablum_activity_taker.xml
│           │                       ├── values/
│           │                       │   ├── taker_colors.xml
│           │                       │   └── taker_strings.xml
│           │                       └── values-zh/
│           │                           └── taker_strings.xml
│           ├── utils/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── ActivityStateUtil.java
│           │                   ├── CallbackFragment.java
│           │                   ├── ColorUtil.java
│           │                   ├── CompressUtil.java
│           │                   ├── Constants.java
│           │                   ├── DateUtil.java
│           │                   ├── DensityUtil.java
│           │                   ├── FileUtil.java
│           │                   ├── PermissionsCallback.java
│           │                   ├── PermissionsFragment.java
│           │                   ├── PermissionsHelper.java
│           │                   ├── Preconditions.java
│           │                   ├── SharedElementHelper.java
│           │                   └── VersionUtil.java
│           ├── watcher/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── DisplayAdapter.java
│           │                   ├── PickedPanelAdapter.java
│           │                   ├── WatcherActivity.java
│           │                   ├── WatcherCallback.java
│           │                   ├── WatcherCallbackLambda.java
│           │                   ├── WatcherConfig.java
│           │                   ├── WatcherContract.java
│           │                   ├── WatcherFragment.java
│           │                   ├── WatcherManager.java
│           │                   ├── WatcherPresenter.java
│           │                   └── res/
│           │                       ├── drawable/
│           │                       │   ├── ic_album_watcher_right_arrow.xml
│           │                       │   └── ic_album_watcher_video_play.xml
│           │                       ├── layout/
│           │                       │   ├── lib_album_activity_watcher.xml
│           │                       │   └── lib_album_fragment_watcher_pager.xml
│           │                       ├── values/
│           │                       │   ├── watcher_colors.xml
│           │                       │   ├── watcher_strings.xml
│           │                       │   └── watcher_themes.xml
│           │                       └── values-zh/
│           │                           └── watcher_strings.xml
│           └── widget/
│               └── com/
│                   └── sharry/
│                       └── lib/
│                           └── album/
│                               ├── CheckedIndicatorView.java
│                               ├── DraggableViewPager.java
│                               ├── PicturePickerFabBehavior.java
│                               ├── RecorderButton.java
│                               ├── photoview/
│                               │   ├── Compat.java
│                               │   ├── CustomGestureDetector.java
│                               │   ├── OnGestureListener.java
│                               │   ├── OnMatrixChangedListener.java
│                               │   ├── OnOutsidePhotoTapListener.java
│                               │   ├── OnPhotoTapListener.java
│                               │   ├── OnScaleChangedListener.java
│                               │   ├── OnSingleFlingListener.java
│                               │   ├── OnViewDragListener.java
│                               │   ├── OnViewTapListener.java
│                               │   ├── PhotoView.java
│                               │   ├── PhotoViewAttacher.java
│                               │   └── Util.java
│                               └── toolbar/
│                                   ├── AppBarHelper.java
│                                   ├── Builder.java
│                                   ├── ImageViewOptions.java
│                                   ├── Options.java
│                                   ├── SToolbar.java
│                                   ├── Style.java
│                                   ├── TextViewOptions.java
│                                   ├── Utils.java
│                                   ├── ViewOptions.java
│                                   └── res/
│                                       └── values/
│                                           └── lib_toolbar_attrs.xml
├── lib-media-recorder/
│   ├── .gitignore
│   ├── CMakeLists.txt
│   ├── Readme.markdown
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── api/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── IMediaRecorder.java
│           │                       ├── IRecorderCallback.java
│           │                       ├── Options.java
│           │                       └── SMediaRecorder.java
│           ├── cpp/
│           │   ├── ConstDefine.h
│           │   ├── JNICall.cpp
│           │   ├── JNICall.h
│           │   ├── OpenSLRecorder.cpp
│           │   ├── OpenSLRecorder.h
│           │   ├── RecordBuffer.cpp
│           │   ├── RecordBuffer.h
│           │   └── native-bridge-recorder.cpp
│           ├── encoder/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── AACEncoder.java
│           │                       ├── EncodeType.java
│           │                       ├── EncoderFactory.java
│           │                       ├── H264Encoder.java
│           │                       ├── H264Render.java
│           │                       ├── IAudioEncoder.java
│           │                       └── IVideoEncoder.java
│           ├── muxer/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── IMuxer.java
│           │                       ├── MPEG4Muxer.java
│           │                       ├── MuxerFactory.java
│           │                       └── MuxerType.java
│           ├── pcmprovider/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── DefaultPCMProvider.java
│           │                       ├── IPCMProvider.java
│           │                       └── OpenSLESPCMProvider.java
│           ├── recorder/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── AudioRecorder.java
│           │                       ├── BaseMediaRecorder.java
│           │                       └── VideoRecorder.java
│           └── utils/
│               └── com/
│                   └── sharry/
│                       └── lib/
│                           └── media/
│                               └── recorder/
│                                   ├── AVPoolExecutor.java
│                                   ├── FileUtil.java
│                                   ├── NetworkUtil.java
│                                   └── VersionUtil.java
├── lib-opengles/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── opengles/
│           │                   ├── surface/
│           │                   │   └── ContextSharedGLSurfaceView.java
│           │                   ├── texture/
│           │                   │   ├── GLTextureView.java
│           │                   │   └── ITextureRenderer.java
│           │                   └── util/
│           │                       ├── EglCore.java
│           │                       ├── FboHelper.java
│           │                       ├── GlMatrixUtil.java
│           │                       └── GlUtil.java
│           └── utils/
│               └── com/
│                   └── sharry/
│                       └── lib/
│                           └── opengles/
│                               ├── EglCore.java
│                               └── GlUtil.java
├── lib-scamera/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── api/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   └── SCameraView.java
│           ├── common/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   ├── AspectRatio.java
│           │                   ├── CameraContext.java
│           │                   ├── Constants.java
│           │                   ├── Size.java
│           │                   └── SizeMap.java
│           ├── device/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   ├── AbsCameraDevice.java
│           │                   ├── Camera1Device.java
│           │                   └── ICameraDevice.java
│           ├── orientation/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   └── ScreenOrientationDetector.java
│           ├── previewer/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   ├── DefaultPreviewerRenderer.java
│           │                   ├── IPreviewer.java
│           │                   ├── Previewer.java
│           │                   ├── PreviewerRendererImpl.java
│           │                   ├── PreviewerRendererWrapper.java
│           │                   └── ScaleType.java
│           └── res/
│               ├── raw/
│               │   ├── camera_fragment_shader.glsl
│               │   └── camera_vertex_shader.glsl
│               └── values/
│                   ├── attrs.xml
│                   ├── public.xml
│                   └── styles.xml
└── settings.gradle

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

================================================
FILE: .gitignore
================================================
# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# Intellij
*.iml
.idea

# Keystore files
*.jks


================================================
FILE: README.md
================================================
## SAlbum
SAlbum 是一款对 Android 端提供 **图片的选取、裁剪、拍摄和短视频录制等功能的图库框架**

## 功能介绍
- **图片的选取**
  - 支持 JPEG/PNG/WEBP/GIF 的选取
  - 图片加载引擎由用户自定义实现
- **图片的浏览**
  - 共享元素跳转动画
- **图像的裁剪**
- **相机的拍摄**
  - ~~CameraX-alpha4 尺寸选取存在问题, 暂时移除~~
  - 提供 1:1、4:3、16:9 的比例选择
     - 支持 CenterCrop 全屏预览
  - 通过自定义 Renderer, 可拓展水印滤镜等效果
- **视频的录制**
  - 视频
    - 使用 MediaCodec 实现 H.264 的硬编
    - 支持 1080p, 720p, 480p 的录制分辨率
  - 音频
    - PCM 数据获取使用 OpenSL ES, 支持 v7a
    - 使用 MediaCodec 硬编为 AAC
  - 使用 MediaMuxer 合并为 mp4 文件
- **视频的播放**
  - 考虑到依赖体积, 使用系统提供的 VideoView 实现
- **已支持 Android 10**
  - Android 10 不支持随意访问外部存储 Storage 中的文件, 可通过 URI 进行图片加载

实现原理请查看 [wiki](https://github.com/SharryChoo/SAlbum/wiki)

## 功能集成
[![](https://jitpack.io/v/SharryChoo/SAlbum.svg)](https://jitpack.io/#SharryChoo/SAlbum)

### Step 1
Add it in your **module build.gradle** at the end of repositories
```
dependencies {
    ...
    // SAlbum dependency
    implementation 'com.github.SharryChoo:SAlbum:+'
     
    // Need Android dependencies
    implementation "androidx.constraintlayout:constraintlayout:+"
    implementation "androidx.appcompat:appcompat:+"
    implementation "androidx.recyclerview:recyclerview:+"
    implementation "com.google.android.material:material:+"
}
```

### Step 2
Add it in your **root build.gradle** at the end of repositories
```
allprojects {
    repositories {
	    ...
	    maven { url 'https://jitpack.io' }
    }
}
```

## 效果展示
下载体验 [Demo](https://raw.githubusercontent.com/SharryChoo/SAlbum/release/assert/SAlbum-1.0.1.apk)

### 资源选取
![资源选取](https://raw.githubusercontent.com/SharryChoo/SAlbum/release/assert/PicturePicker.jpg)

### 图像拍摄
![图像拍摄](https://raw.githubusercontent.com/SharryChoo/SAlbum/release/assert/PictureTaker.png)

### 视频录制
![视频录制](https://raw.githubusercontent.com/SharryChoo/SAlbum/release/assert/VideoRecord.png)

### 视频播放
![视频的播放](https://raw.githubusercontent.com/SharryChoo/SAlbum/release/assert/VideoPlay.jpg)

## 使用指南
SPicturePicker 的所有功能提供, 均通过 **Manager** 对外提供, 其具体的功能选项通过 **Config** 来配置

功能 | Manager | Config
:---:|:---:|:---:
选取 | PickerManager | PickerConfig
浏览 | WatcherManager | WatcherConfig
拍摄/录像 | TakerManager | TakerConfig
裁剪 | CropperManager | CropperConfig

### 一) 选取
```
PickerManager.with(context)
        // 注入配置
        .setPickerConfig(
                PickerConfig.Builder()
                        // Toolbar 背景设置
                        .setToolbarBackgroundColor(
                                ContextCompat.getColor(this, R.color.colorPrimary)
                        )
                        // 指示器填充色
                        .setIndicatorSolidColor(
                                ContextCompat.getColor(this, R.color.colorPrimary)
                        )
                        // 选中指示器的颜色
                        .setIndicatorBorderColor(
                                ContextCompat.getColor(this, R.color.colorPrimary),
                                ContextCompat.getColor(this, android.R.color.white)
                        )
                        // 指示器边界的颜色
                        .setPickerItemBackgroundColor(
                                ContextCompat.getColor(this, android.R.color.white)
                        )
                        // 阈值
                        .setThreshold(etAlbumThreshold.text.toString().toInt())
                        // 每行展示的数量
                        .setSpanCount(etSpanCount.text.toString().toInt())
                        // 是否开启 Toolbar Behavior 动画
                        .isToolbarScrollable(cbAnimation.isChecked)
                        // 是否开启 Fab Behavior 动画
                        .isFabScrollable(cbAnimation.isChecked)
                        // 是否选择 GIF 图
                        .isPickGif(cbGif.isChecked)
                        // 是否选择视频
                        .isPickVideo(cbVideo.isChecked)
                        // 注入用户已选中的图片集合
                        .setUserPickedSet(mPickedSet)
                        // 设置相机配置, 非 null 说明支持相机(拍摄/录制)
                        .setCameraConfig(
                                if (cbCamera.isChecked) takerConfig else null
                        )
                        // 设置裁剪配置, 非 null 说明支持裁剪
                        .setCropConfig(
                                if (cbCrop.isChecked) cropperConfig else null
                        )
                        .build()
        )
        // 加载框架注入
        .setLoaderEngine(
                object : ILoaderEngine {
                    override fun loadPicture(context: Context, mediaMeta: MediaMeta, imageView: ImageView) {
                        // Android 10 以后, 需要使用 URI 进行加载
                        Glide.with(context).asBitmap().load(mediaMeta.contentUri).into(imageView)
                    }

                    override fun loadGif(context: Context, mediaMeta: MediaMeta, imageView: ImageView) {
                        // Android 10 以后, 需要使用 URI 进行加载
                        Glide.with(context).asGif().load(mediaMeta.contentUri).into(imageView)
                    }

                    override fun loadVideoThumbnails(context: Context, mediaMeta: MediaMeta, imageView: ImageView) {
                        // Android 10 以后, 需要使用 URI 进行加载
                        Glide.with(context).asBitmap().load(mediaMeta.contentUri).into(imageView)
                    }
                }
        )
        .start {
            // TODO 选中的资源, 通过 ArrayList<MediaMeta> 返回
        }
```
选取的方式如上所示, **首先按照需求构建 Config**, **然后注入图片加载的引擎**, 之后便可以在 start 的回调中获取到选中的图片资源了
- 关于相机
  - 在 PickerConfig 中传入相机的配置, 则意为开启相机的功能
- 关于裁剪
  - 在 PickerConfig 中传入裁剪的配置, 则意为开启裁剪的功能

### 二) 浏览
浏览的功能与选取类似, 打开图片选择器时, 会根据 PickerConfig 自动生成浏览的配置, 若想在外界单独使用图片浏览的功能, 可以通过以下方式
```
WatcherManager.with(context)
        .setConfig(
            WatcherConfig.Builder()
                // 配置 Indicator 的展示效果
                .setIndicatorTextColor(mPickerConfig.getIndicatorTextColor())
                .setIndicatorSolidColor(mPickerConfig.getIndicatorSolidColor())
                .setIndicatorBorderColor(
                        mPickerConfig.getIndicatorBorderCheckedColor(),
                        mPickerConfig.getIndicatorBorderUncheckedColor()
                )
                // 注入需要展示的图片
                .setDisplayDataSet(mPickedSet, 0)
                // 设置最大选中数量, 若 > 0, 则说明图片查看器也支持选取的功能
                .setThreshold(mPickerConfig.getThreshold())
                // 注入用户选中的图片集合
                .setUserPickedSet(mPickedSet)
                .build();
        )
        // 注入共享元素
        .setSharedElement(sharedElement)
        // 注入图片加载器
        .setLoaderEngine(Loader.getPictureLoader())
        .startForResult(this);
```
可以看到浏览的使用主要区别在于, 增加了
**共享元素(支持 5.0 以下的操作系统)**
的选项
- 当 threshold > 0 时, 表示需要为图片浏览添加图片选择功能, 反之仅做图片查看使用


### 三) 拍摄
相机的使用与浏览类似, 可以集成在 Picker 中使用, 也可以单独使用
```
TakerManager.with(context)
        .setConfig(
            TakerConfig.Builder()
                // 设置外部存储目录相对路径
                .setRelativePath(RELATIVE_PATH)
                // 指定 FileProvider 的 authority, 用于获取文件 URI
                .setAuthority(FILE_PROVIDER)
                // 预览画面比例, 支持 1:1, 4:3, 16:9
                .setPreviewAspect(ASPECT_1_1)
                // 是否全屏预览(在比例基础上进行 CenterCrop, 保证画面不畸形)
                .setFullScreen(false)
                // 设置自定义 Renderer 的路径
                .setRenderer(WatermarkPreviewerRenderer::class.java)
                // 设置是否支持视频录制
                .setVideoRecord(true)
                // 设置录制最大时长
                .setMaxRecordDuration(15 * 1000)
                // 设置录制最短时长
                .setMinRecordDuration(1 * 1000)
                // 设置录制的分辨率
                .setRecordResolution(Options.Video.RESOLUTION_720P)
                // 拍摄后质量压缩
                .setPictureQuality(80)
                // 注入裁剪配置, 非 null, 表示拍摄之后进行图片的裁剪
                .setCropConfig(...)
                .build()
        )
        .take(this);
```
其中的注释比较清晰, 操作完成之后, 可通过回调获取到拍摄/录制的结果

#### 1. RelativePath 
Andorid 10 以后, 无法随意的在外部存储卡中创建文件, 因此使用了 RelativePath 
```
// 绝对路径
"/storage/emulated/0/{@link android.os.Environment#DIRECTORY_PICTURES}/SAlbum"
// 相对路径
"SAlbum"
```
只需要设置了相对路径, SAlbum 会自动在 Android 媒体文件夹下创建工程的文件夹, 所有拍摄录制的图片均会保存在其中, 这也是 Android 希望我们遵守的规范

#### 2. Authority
需要获取文件的 URI, 7.0 之后获取 URI 需要通过 FileProvider, 因此这里需要传入 FileProvider 的 authority, 关于这一块网上的资料比较多, 这里就不再赘述了

#### 3. Camera 渲染器
**关于自定义 Camera 的渲染器, 需要用户自定义实现 IPreviewer.Renderer 这个接口**, Demo 中提供了一个水印效果的渲染器滤镜, 可以其参考实现自己的渲染器
```
public Builder setRenderer(@NonNull Class<? extends IPreviewer.Renderer> rendererClass) {
    try {
        rendererClass.getDeclaredConstructor(Context.class);
    } catch (NoSuchMethodException e) {
        throw new UnsupportedOperationException("Please ensure " + rendererClass.getSimpleName()
                + " have a constructor like: " + rendererClass.getSimpleName() + "(Context context)");
    }
    mConfig.rendererClsName = rendererClass.getName();
    return this;
}
```
传入渲染器实现的 class 文件, 需要保证提供一个参数为 Context 的构造方法, 否则在构建 TakerConfig 时会出现异常

### 四) 裁剪
```
CropperManager.with(context)
        .setConfig(
            CropperConfig.Builder()
                // 要裁剪的图片的 URI
                .setOriginUri(...)
                // 指定 FileProvider 的 authority, 用于 7.0 获取文件 URI
                .setAuthority(FILE_PROVIDER)
                // 设置外部存储目录相对路径
                .setRelativePath(RELATIVE_PATH)
                // 裁剪期望的尺寸
                .setCropSize(1000, 1000)
                // 裁剪后的质量
                .setCropQuality(80)
                .build()
        )
        .crop(this);
```
裁剪目前使用系统提供的裁剪方式, 其使用方式也比较简单, 这里就不再赘述了

更多功能请查看工程中提供的[示例](https://github.com/SharryChoo/SAlbum/blob/release/app/src/main/java/com/sharry/app/salbum/MainActivity.kt)

## 致谢
[PhotoView](https://github.com/chrisbanes/PhotoView)


================================================
FILE: app/.gitignore
================================================
/build
# Built application files
*.apk
*.ap_

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# Intellij
*.iml
.idea/workspace.xml

# Keystore files
*.jks

================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion rootProject.compileSdkVersion
    defaultConfig {
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        vectorDrawables.useSupportLibrary true
        externalNativeBuild {
            ndk {
                abiFilters "armeabi-v7a"
            }
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
    // Google dependencies
    def constraintlayoutVersion = "1.1.3"
    implementation "androidx.constraintlayout:constraintlayout:$constraintlayoutVersion"
    def supportLibraryVersion = '1.1.0'
    implementation "androidx.appcompat:appcompat:$supportLibraryVersion"
    def recycleViewVersion = '1.0.0'
    implementation "androidx.recyclerview:recyclerview:$recycleViewVersion"
    def materialVersion = '1.0.0'
    implementation "com.google.android.material:material:$materialVersion"

    // Glide dependencies
    def glideVersion = '4.6.1'
    implementation "com.github.bumptech.glide:glide:$glideVersion"
    kapt "com.github.bumptech.glide:compiler:$glideVersion"

    // SPicturePicker dependencies
//    implementation 'com.github.SharryChoo:SAlbum:1.0.0'
    api project(':lib-album')

}

================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# 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 *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile


================================================
FILE: app/release/output.json
================================================
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":-1,"enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.sharry.app.salbum">

    <!-- sd卡写权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.sharry.app.salbum.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

================================================
FILE: app/src/main/java/com/sharry/app/salbum/MainActivity.kt
================================================
package com.sharry.app.salbum

import android.content.Context
import android.os.Bundle
import android.text.TextUtils
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide
import com.sharry.lib.album.*
import com.sharry.lib.album.TakerConfig.ASPECT_4_3
import com.sharry.lib.album.toolbar.SToolbar
import com.sharry.lib.media.recorder.Options
import kotlinx.android.synthetic.main.app_activity_main.*


/**
 * SAlbum 示例 Activity.
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 12/6/2018 10:49 AM
 */
private const val FILE_PROVIDER = "com.sharry.app.salbum.FileProvider"
private const val RELATIVE_PATH = "SAlbum"

class MainActivity : AppCompatActivity() {

    /**
     * 用与图片选取的配置
     */
    private lateinit var pickerConfig: PickerConfig

    /**
     * 用与相机拍摄的配置
     */
    private val takerConfig = TakerConfig.Builder()
            // 指定 FileProvider 的 authority, 用于 7.0 获取文件 URI
            .setAuthority(FILE_PROVIDER)
            // 设置外部存储目录相对路径
            .setRelativePath(RELATIVE_PATH)
            // 预览画面比例
            .setPreviewAspect(ASPECT_4_3)
            // 是否全屏预览(在比例基础上进行 CenterCrop, 保证画面不畸形)
            .setFullScreen(true)
            // 设置自定义 Renderer 的实现类
            .setRenderer(WatermarkPreviewerRenderer::class.java)
            // 设置是否支持视频录制
            .setVideoRecord(true)
            // 是否仅支持视频录制
            .setJustVideoRecord(false)
            // 设置录制最大时长
            .setMaxRecordDuration(15 * 1000)
            // 设置录制最短时长
            .setMinRecordDuration(1 * 1000)
            // 设置录制的分辨率
            .setRecordResolution(Options.Video.RESOLUTION_1080P)
            // 拍摄后质量压缩
            .setPictureQuality(80)
            .build()

    /**
     * 用与裁剪的配置
     */
    private val cropperConfig = CropperConfig.Builder()
            // 指定 FileProvider 的 authority, 用于 7.0 获取文件 URI
            .setAuthority(FILE_PROVIDER)
            // 设置外部存储目录相对路径
            .setRelativePath(RELATIVE_PATH)
            // 裁剪期望的尺寸
            .setCropSize(1000, 1000)
            // 裁剪后的质量
            .setCropQuality(80)
            .build()

    /**
     * 图片加载器
     *
     * 注: Android 10 以后需要使用 URI 进行加载操作
     */
    private val pictureLoader = object : ILoaderEngine {
        override fun loadPicture(context: Context, mediaMeta: MediaMeta, imageView: ImageView) {
            // Android 10 以后, 需要使用 URI 进行加载
            Glide.with(context).asBitmap().load(mediaMeta.contentUri).into(imageView)
        }

        override fun loadGif(context: Context, mediaMeta: MediaMeta, imageView: ImageView) {
            // Android 10 以后, 需要使用 URI 进行加载
            Glide.with(context).asGif().load(mediaMeta.contentUri).into(imageView)
        }

        override fun loadVideoThumbnails(context: Context, mediaMeta: MediaMeta, imageView: ImageView) {
            // Android 10 以后, 需要使用 URI 进行加载
            Glide.with(context).asBitmap().load(mediaMeta.contentUri).into(imageView)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.app_activity_main)
        initTitle()
        initViews()
        initData()
    }

    private fun initTitle() {
        SToolbar.Builder(this)
                .setBackgroundColorRes(R.color.colorPrimary)
                .setTitleText(getString(R.string.app_name))
                .apply()
    }

    private fun initViews() {
        btnLaunchAlbum.setOnClickListener { _ ->
            if (TextUtils.isEmpty(etAlbumThreshold.text) || TextUtils.isEmpty(etSpanCount.text)) {
                return@setOnClickListener
            }
            openAlbum()
        }
    }

    private fun initData() {
        pickerConfig = PickerConfig.Builder()
                // Toolbar 背景设置
                .setToolbarBackgroundColor(ContextCompat.getColor(this, R.color.colorPrimary))
                // 指示器填充色
                .setIndicatorSolidColor(ContextCompat.getColor(this, R.color.colorPrimary))
                // 指示器边界的颜色
                .setPickerItemBackgroundColor(ContextCompat.getColor(this, android.R.color.white))
                // 选中指示器的颜色
                .setIndicatorBorderColor(
                        ContextCompat.getColor(this, R.color.colorPrimary),
                        ContextCompat.getColor(this, android.R.color.white)
                )
                .build()
    }

    private fun openAlbum() {
        // 根据选择中数据重新构建 pickerConfig.
        pickerConfig.rebuild()
                // 阈值
                .setThreshold(etAlbumThreshold.text.toString().toInt())
                // 每行展示的数量
                .setSpanCount(etSpanCount.text.toString().toInt())
                // 是否开启 Toolbar Behavior 动画
                .isToolbarScrollable(cbAnimation.isChecked)
                // 是否开启 Fab Behavior 动画
                .isFabScrollable(cbAnimation.isChecked)
                // 是否选择图片
                .isPickPicture(cbPicture.isChecked)
                // 是否选择 GIF 图
                .isPickGif(cbGif.isChecked)
                // 是否选择视频
                .isPickVideo(cbVideo.isChecked)
                // 设置相机配置, 非 null 说明支持相机(拍摄/录制)
                .setCameraConfig(if (cbCamera.isChecked) takerConfig else null)
                // 设置裁剪配置, 非 null 说明支持裁剪
                .setCropConfig(if (cbCrop.isChecked) cropperConfig else null)
                .build()
        PickerManager.with(this)
                // 设置选择配置文件
                .setPickerConfig(pickerConfig)
                // 图片加载框架注入
                .setLoaderEngine(pictureLoader)
                // 开始选取
                .start {
                    it?.forEach {
                        Toast.makeText(this, it.toString(), Toast.LENGTH_SHORT).show()
                    }
                }
    }

}


================================================
FILE: app/src/main/java/com/sharry/app/salbum/WatermarkPreviewerRenderer.java
================================================
package com.sharry.app.salbum;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;

import com.sharry.lib.camera.PreviewerRendererImpl;
import com.sharry.lib.camera.PreviewerRendererWrapper;
import com.sharry.lib.opengles.util.FboHelper;
import com.sharry.lib.opengles.util.GlUtil;

import java.nio.FloatBuffer;

/**
 * 带水印效果的渲染器
 * <p>
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2019-08-01 16:04
 */
public class WatermarkPreviewerRenderer extends PreviewerRendererWrapper {

    private static final String VERTEX_SHADER_STR = "attribute vec4 aVertexPosition;\n" +
            "    attribute vec2 aTexturePosition;\n" +
            "    varying vec2 vPosition;\n" +
            "    void main() {\n" +
            "        vPosition = aTexturePosition;\n" +
            "        gl_Position = aVertexPosition;\n" +
            "    }";


    private static final String FRAGMENT_SHADER_STR = "precision mediump float;\n" +
            "varying vec2 vPosition;\n" +
            "uniform sampler2D uTexture;\n" +
            "void main() {\n" +
            "    gl_FragColor=texture2D(uTexture, vPosition);\n" +
            "}";

    /**
     * 相机顶点坐标
     */
    private final float[] mCameraVertexCoords = new float[]{
            -1f, 1f,  // 左上
            -1f, -1f, // 左下
            1f, 1f,   // 右上
            1f, -1f,   // 右下
    };

    /**
     * 相机纹理映射坐标
     */
    private final float[] mCameraTextureCoords = new float[]{
            0f, 1f,   // 左上
            0f, 0f,   // 左下
            1f, 1f,   // 右上
            1f, 0f    // 右下
    };

    /**
     * 水印顶点坐标
     */
    private final float[] mWatermarkVertexCoords = new float[]{
            0f, 0f,  // 左上
            0f, 0f,  // 左下
            0f, 0f,  // 右上
            0f, 0f,  // 右下
    };

    /**
     * 水印纹理坐标, 水印从 Bitmap 中加载, 坐标系相反
     */
    private final float[] mWatermarkTextureCoords = new float[]{
            0f, 0f,   // 左下
            0f, 1f,   // 左上
            1f, 0f,   // 右下
            1f, 1f    // 右上
    };

    /**
     * 相机纹理顶点和纹理坐标
     */
    private final FloatBuffer mCameraTextureVertexBuffer = GlUtil.createFloatBuffer(mCameraVertexCoords);
    private final FloatBuffer mCameraTextureBuffer = GlUtil.createFloatBuffer(mCameraTextureCoords);

    /**
     * 水印纹理顶点和纹理坐标
     */
    private final FloatBuffer mWatermarkVertexBuffer = GlUtil.createFloatBuffer(mWatermarkVertexCoords);
    private final FloatBuffer mWatermarkTextureBuffer = GlUtil.createFloatBuffer(mWatermarkTextureCoords);

    private final Context mContext;
    private final FboHelper mFboHelper;
    private int mProgramId;
    private int aVertexPosition;
    private int aTexturePosition;
    private int mVboId;
    private int uTexture;
    private int mWatermarkTextureId = 0;
    private Bitmap mWatermarkBitmap;

    public WatermarkPreviewerRenderer(Context context) {
        super(new PreviewerRendererImpl(context));
        this.mContext = context;
        this.mFboHelper = new FboHelper();
    }

    @Override
    public void onAttach() {
        super.onAttach();
        mFboHelper.onAttach();
        // 初始化程序
        setupShaders();
        // 初始化顶点坐标
        setupCoordinates();
        // 初始化水印纹理
        setupWatermarkTexture();
    }

    private void setupShaders() {
        mProgramId = GlUtil.createProgram(VERTEX_SHADER_STR, FRAGMENT_SHADER_STR);
        aVertexPosition = GLES20.glGetAttribLocation(mProgramId, "aVertexPosition");
        aTexturePosition = GLES20.glGetAttribLocation(mProgramId, "aTexturePosition");
        uTexture = GLES20.glGetUniformLocation(mProgramId, "uTexture");
    }

    private void setupCoordinates() {
        // 创建 vbo
        int vboSize = 1;
        int[] vboIds = new int[vboSize];
        GLES20.glGenBuffers(vboSize, vboIds, 0);
        // 将顶点坐标写入 vbo
        mVboId = vboIds[0];
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId);
        // 开辟 VBO 空间
        GLES20.glBufferData(
                GLES20.GL_ARRAY_BUFFER,
                mCameraVertexCoords.length * 4 +
                        mCameraTextureCoords.length * 4
                        + mWatermarkVertexCoords.length * 4
                        + mWatermarkTextureCoords.length * 4,
                null,
                GLES20.GL_STATIC_DRAW
        );
        // 写入相机顶点坐标
        GLES20.glBufferSubData(
                GLES20.GL_ARRAY_BUFFER,
                0,
                mCameraVertexCoords.length * 4,
                mCameraTextureVertexBuffer
        );
        // 写入相机纹理坐标
        GLES20.glBufferSubData(
                GLES20.GL_ARRAY_BUFFER,
                mCameraVertexCoords.length * 4,
                mCameraTextureCoords.length * 4,
                mCameraTextureBuffer
        );
        // 写入水印顶点坐标
        GLES20.glBufferSubData(
                GLES20.GL_ARRAY_BUFFER,
                mCameraVertexCoords.length * 4 +
                        mCameraTextureCoords.length * 4,
                mWatermarkVertexCoords.length * 4,
                mWatermarkVertexBuffer
        );
        // 写入水印纹理坐标
        GLES20.glBufferSubData(
                GLES20.GL_ARRAY_BUFFER,
                mCameraVertexCoords.length * 4 +
                        mCameraTextureCoords.length * 4 +
                        mWatermarkVertexCoords.length * 4,
                mWatermarkTextureCoords.length * 4,
                mWatermarkTextureBuffer
        );
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
    }

    private void setupWatermarkTexture() {
        int[] textureIds = new int[1];
        GLES20.glGenTextures(1, textureIds, 0);
        mWatermarkTextureId = textureIds[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mWatermarkTextureId);
        // 设置纹理环绕方式
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        // 设置纹理过滤方式
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        // 创建 Bitmap, 将其写入纹理
        mWatermarkBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_demo_watermark);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mWatermarkBitmap, 0);
        // 解绑
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    }

    @Override
    public void onSizeChanged(int width, int height) {
        super.onSizeChanged(width, height);
        mFboHelper.onSizeChanged(width, height);
        // 启用透明
        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
        GLES20.glViewport(0, 0, width, height);
        // 更新水印坐标
        updateWatermarkCoors(width, height);
    }

    private void updateWatermarkCoors(int surfaceWidth, int surfaceHeight) {
        float height = mWatermarkBitmap.getHeight();
        float width = mWatermarkBitmap.getWidth();
        height = height * (1 / (float) surfaceHeight);
        width = width * (1 / (float) surfaceWidth);
        float left = -0.9f;
        float bottom = -0.9f;
        // 设置水印的位置
        // 左上
        mWatermarkVertexCoords[0] = left;
        mWatermarkVertexCoords[1] = bottom + height;
        // 左下
        mWatermarkVertexCoords[2] = left;
        mWatermarkVertexCoords[3] = bottom;
        // 右上
        mWatermarkVertexCoords[4] = left + width;
        mWatermarkVertexCoords[5] = bottom + height;
        // 右下
        mWatermarkVertexCoords[6] = left + width;
        mWatermarkVertexCoords[7] = bottom;
        // 更新 Buffer
        mWatermarkVertexBuffer.put(mWatermarkVertexCoords, 0, mWatermarkVertexCoords.length)
                .position(0);
        // 更新 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId);
        // 写入水印顶点坐标
        GLES20.glBufferSubData(
                GLES20.GL_ARRAY_BUFFER,
                mCameraVertexCoords.length * 4 +
                        mCameraTextureCoords.length * 4,
                mWatermarkVertexCoords.length * 4,
                mWatermarkVertexBuffer
        );
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
    }

    @Override
    protected void onDrawTexture(int textureId) {
        mFboHelper.bindFramebuffer();
        // 绘制纹理
        drawOriginTexture(textureId);
        // 绘制水印
        drawWatermark();
        // 解绑
        mFboHelper.unbindFramebuffer();
        // 绘制到系统自带的缓冲上
        drawToEGLSurface();
    }

    private void drawOriginTexture(int textureId) {
        GLES20.glUseProgram(mProgramId);
        // 绑定相机的纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        // 写入顶点坐标
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId);
        GLES20.glEnableVertexAttribArray(aVertexPosition);
        GLES20.glVertexAttribPointer(aVertexPosition, 2, GLES20.GL_FLOAT, false, 8, 0);
        // 写入纹理坐标
        GLES20.glEnableVertexAttribArray(aTexturePosition);
        GLES20.glVertexAttribPointer(aTexturePosition, 2, GLES20.GL_FLOAT, false,
                8, mCameraVertexCoords.length * 4);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        // 给 uTexture 赋值
        GLES20.glUniform1i(uTexture, 0);
        // 绘制到屏幕
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        // 解绑纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    }

    private void drawWatermark() {
        GLES20.glUseProgram(mProgramId);
        // 绑定纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mWatermarkTextureId);
        // 写入水印顶点坐标
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId);
        GLES20.glEnableVertexAttribArray(aVertexPosition);
        GLES20.glVertexAttribPointer(aVertexPosition, 2, GLES20.GL_FLOAT, false,
                8, (mCameraVertexCoords.length + mCameraTextureCoords.length) * 4);
        // 写入水印纹理坐标
        GLES20.glEnableVertexAttribArray(aTexturePosition);
        GLES20.glVertexAttribPointer(
                aTexturePosition,
                2,
                GLES20.GL_FLOAT,
                false,
                8,
                (mCameraVertexCoords.length + mCameraTextureCoords.length + mWatermarkVertexCoords.length) * 4
        );
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        // 给 uTexture 赋值
        GLES20.glUniform1i(uTexture, 0);
        // 绘制到屏幕
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        // 解绑纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    }

    private void drawToEGLSurface() {
        GLES20.glUseProgram(mProgramId);
        // 绑定纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getPreviewerTextureId());
        // 写入顶点坐标
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId);
        GLES20.glEnableVertexAttribArray(aVertexPosition);
        GLES20.glVertexAttribPointer(aVertexPosition, 2, GLES20.GL_FLOAT, false,
                8, 0);
        // 写入纹理坐标
        GLES20.glEnableVertexAttribArray(aTexturePosition);
        GLES20.glVertexAttribPointer(aTexturePosition, 2, GLES20.GL_FLOAT, false,
                8, mCameraVertexCoords.length * 4);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        // 给 uTexture 赋值
        GLES20.glUniform1i(uTexture, 0);
        // 绘制到屏幕
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        // 解绑纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    }

    @Override
    public int getPreviewerTextureId() {
        return mFboHelper.getTexture2DId();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mFboHelper.onDetach();
        // 释放着色器程序
        if (mProgramId != 0) {
            GLES20.glDeleteProgram(mProgramId);
        }
        // 释放 VBO
        if (mVboId != 0) {
            int size = 1;
            int[] vboIds = new int[size];
            vboIds[0] = mVboId;
            GLES20.glDeleteBuffers(1, vboIds, 0);
        }
        // 释放纹理
        if (mWatermarkTextureId != 0) {
            int size = 1;
            int[] textures = new int[size];
            textures[0] = mWatermarkTextureId;
            GLES20.glDeleteTextures(1, textures, 0);
        }
    }
}


================================================
FILE: app/src/main/res/drawable/app_activity_main_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <corners android:radius="6dp"/>

    <solid android:color="@color/colorAccent" />

</shape>

================================================
FILE: app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="108dp"
        android:height="108dp"
        android:viewportWidth="890.43475"
        android:viewportHeight="890.43475">
    <group android:translateX="-66.78261"
            android:translateY="-66.78261">
      <path
          android:pathData="M521.15,511.94m-416,0a416,416 0,1 0,832 0,416 416,0 1,0 -832,0Z"
          android:fillColor="#AEECFF"/>
      <path
          android:pathData="M733.38,205.06l-44.61,-44.22L111.94,434.56s-21.44,80.19 2.75,177.41c166.98,-107.97 503.55,-330.69 618.69,-406.91zM653.57,308.42L373.31,557.31l93.38,57.41 232,-273.41zM731.9,351.87l-116.35,282.88h104l56,-266.82zM818.69,331.14v324.86l100.67,-23.62 -60.99,-301.7z"
          android:fillColor="#E3FAFF"/>
      <path
          android:pathData="M774.21,241.22m-145.22,0a145.22,145.22 0,1 0,290.43 0,145.22 145.22,0 1,0 -290.43,0Z"
          android:fillColor="#FFFF5F"/>
      <path
          android:pathData="M521.09,928c108.16,0 206.4,-41.6 280.32,-109.31a110.08,110.08 0,0 0,-27.65 -47.55L447.81,445.12a112.51,112.51 0,0 0,-158.66 0L118.66,615.55c46.14,179.58 208.58,312.45 402.43,312.45z"
          android:fillColor="#66BF47"/>
      <path
          android:pathData="M316.74,874.05a413.5,413.5 0,0 0,204.35 53.95,415.81 415.81,0 0,0 400.26,-303.74l-101.63,-101.57a112.51,112.51 0,0 0,-158.66 0l-325.95,326.02c-7.62,7.62 -13.44,16.32 -18.37,25.34z"
          android:fillColor="#3FB018"/>
      <path
          android:pathData="M521.09,928a413.76,413.76 0,0 0,186.43 -44.48l-221.25,-221.18a128.32,128.32 0,0 0,-180.99 0l-109.06,109.06A414.85,414.85 0,0 0,521.09 928z"
          android:fillColor="#5AA93E"/>
    </group>
</vector>


================================================
FILE: app/src/main/res/layout/app_activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp">

        <EditText
            android:id="@+id/etAlbumThreshold"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@null"
            android:hint="@string/app_activity_main_et_threshold_hint"
            android:inputType="number"
            android:padding="10dp"
            android:text="6"
            android:textColor="@color/colorAccent"
            android:textSize="15dp" />

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp">

        <EditText
            android:id="@+id/etSpanCount"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@null"
            android:hint="@string/app_activity_main_et_span_count_hint"
            android:inputType="number"
            android:padding="10dp"
            android:text="3"
            android:textColor="@color/colorAccent"
            android:textSize="15dp" />

    </com.google.android.material.textfield.TextInputLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp">

        <CheckBox
            android:id="@+id/cbPicture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/app_activity_main_cb_picture" />

        <CheckBox
            android:id="@+id/cbGif"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:text="@string/app_activity_main_cb_gif" />

        <CheckBox
            android:id="@+id/cbVideo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:text="@string/app_activity_main_cb_video" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp">

        <CheckBox
            android:id="@+id/cbCamera"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/app_activity_main_cb_camera" />

        <CheckBox
            android:id="@+id/cbCrop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:text="@string/app_activity_main_cb_crop" />

        <CheckBox
            android:id="@+id/cbAnimation"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:text="@string/app_activity_main_cb_anim" />

    </LinearLayout>

    <Button
        android:id="@+id/btnLaunchAlbum"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="30dp"
        android:background="@drawable/app_activity_main_launcher"
        android:gravity="center"
        android:text="@string/activity_btn_launch"
        android:textAllCaps="false"
        android:textColor="#fff"
        android:textSize="20dp" />

</LinearLayout>

================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background"/>
    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background"/>
    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimaryDark">#00b0ff</color>
    <color name="colorPrimary">#00b0ff</color>
    <color name="colorAccent">#64b6f6</color>
</resources>


================================================
FILE: app/src/main/res/values/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="ic_launcher_background">#3576BB</color>
</resources>

================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
    <string name="app_name" translatable="false">SAlbum</string>

    <string name="app_activity_main_et_threshold_hint">Threshold</string>
    <string name="app_activity_main_et_span_count_hint">Span Count</string>

    <string name="app_activity_main_cb_picture">Picture</string>
    <string name="app_activity_main_cb_gif">Gif</string>
    <string name="app_activity_main_cb_video">Video</string>
    <string name="app_activity_main_cb_camera">Camera</string>
    <string name="app_activity_main_cb_crop">Crop</string>
    <string name="app_activity_main_cb_anim">Animation</string>
    <string name="activity_btn_launch">Launcher</string>
</resources>


================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>


================================================
FILE: app/src/main/res/values-zh/strings.xml
================================================
<resources>

    <string name="app_activity_main_et_threshold_hint">选中数量</string>
    <string name="app_activity_main_et_span_count_hint">每行数量</string>

    <string name="app_activity_main_cb_picture">图片</string>
    <string name="app_activity_main_cb_gif">Gif</string>
    <string name="app_activity_main_cb_video">视频</string>
    <string name="app_activity_main_cb_camera">相机</string>
    <string name="app_activity_main_cb_crop">裁剪</string>
    <string name="app_activity_main_cb_anim">动画</string>
    <string name="activity_btn_launch">启动</string>

</resources>


================================================
FILE: app/src/main/res/xml/provider_paths.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_files"
        path="." />
    <cache-path
        name="cache_files"
        path="." />
    <files-path
        name="files_files"
        path="." />
</paths>

================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()
    }
    // Define versions in a single place
    ext {
        // Sdk and tools
        compileSdkVersion = 29
        minSdkVersion = 19
        targetSdkVersion = 29
        buildToolsVersion = '29.0.0'

        // Root project dependencies
        gradleVersion = '3.2.0'
        kotlinVersion = '1.2.51'
        mavenVersion = '2.1'

        // common dependencies
        supportLibraryVersion = '1.1.0'
    }
    dependencies {
        classpath "com.android.tools.build:gradle:$gradleVersion"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
        classpath "com.github.dcendents:android-maven-gradle-plugin:$mavenVersion"
    }
}

tasks.withType(JavaCompile) {
    options.encodin g = 'UTF-8'
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}

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


================================================
FILE: git
================================================


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sun Apr 28 15:08:24 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-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: lib-album/.gitignore
================================================
/build
# Built application files
*.apk
*.ap_

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# Intellij
*.iml
.idea/workspace.xml

# Keystore files
*.jks

================================================
FILE: lib-album/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'

group = 'com.github.SharryChoo'

def resDirs = [
        "watcher/com/sharry/lib/album",
        "picker/com/sharry/lib/album",
        "taker/com/sharry/lib/album",
        "player/com/sharry/lib/album",
        "widget/com/sharry/lib/album/toolbar"
]

android {
    compileSdkVersion rootProject.compileSdkVersion
    defaultConfig {
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        vectorDrawables.useSupportLibrary true
    }
    sourceSets {
        main {
            java.srcDirs += 'src/main/base'
            java.srcDirs += 'src/main/picker'
            java.srcDirs += 'src/main/watcher'
            java.srcDirs += 'src/main/player'
            java.srcDirs += 'src/main/copper'
            java.srcDirs += 'src/main/taker'
            java.srcDirs += 'src/main/widget'
            java.srcDirs += 'src/main/utils'
            resDirs.forEach {
                res.srcDirs += 'src/main/' + it + '/res'
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // Android dependencies
    def constraintlayoutVersion = "1.1.3"
    implementation "androidx.constraintlayout:constraintlayout:$constraintlayoutVersion"
    def supportLibraryVersion = '1.1.0'
    implementation "androidx.appcompat:appcompat:$supportLibraryVersion"
    def recycleViewVersion = '1.0.0'
    implementation "androidx.recyclerview:recyclerview:$recycleViewVersion"
    def materialVersion = '1.0.0'
    implementation "com.google.android.material:material:$materialVersion"
    // Core dependencies.
    api project(':lib-media-recorder')
}

================================================
FILE: lib-album/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

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

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile


================================================
FILE: lib-album/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sharry.lib.album">

    <!-- SD 卡读写权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:supportsRtl="true">
        <activity
            android:name="com.sharry.lib.album.PickerActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            android:theme="@style/PickerTheme" />
        <activity
            android:name="com.sharry.lib.album.WatcherActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            android:theme="@style/WatcherTheme" />
        <activity
            android:name="com.sharry.lib.album.TakerActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait" />
        <activity
            android:name="com.sharry.lib.album.VideoPlayerActivity"
            android:launchMode="singleTop" />
    </application>

</manifest>

================================================
FILE: lib-album/src/main/base/com/sharry/lib/album/ILoaderEngine.java
================================================
package com.sharry.lib.album;

import android.content.Context;
import android.widget.ImageView;

import androidx.annotation.NonNull;

/**
 * 图片加载的接口, 由外界实现图片加载的策略
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/9/18 16:18
 */
public interface ILoaderEngine {

    /**
     * 加载图片的实现
     */
    void loadPicture(@NonNull Context context, @NonNull MediaMeta mediaMeta, @NonNull ImageView imageView);

    /**
     * 加载 Gif 图
     */
    void loadGif(@NonNull Context context, @NonNull MediaMeta mediaMeta, @NonNull ImageView imageView);

    /**
     * 加载视频缩略图
     */
    void loadVideoThumbnails(@NonNull Context context, @NonNull MediaMeta mediaMeta, @NonNull ImageView imageView);

}


================================================
FILE: lib-album/src/main/base/com/sharry/lib/album/Loader.java
================================================
package com.sharry.lib.album;

import android.content.Context;
import android.util.Log;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * PicturePicker 加载图片的工具类
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/6/21 16:19
 */
final class Loader {

    private static final String TAG = Loader.class.getSimpleName();
    private static ILoaderEngine sEngine;

    static void setLoaderEngine(@Nullable ILoaderEngine engine) {
        if (engine != null) {
            sEngine = engine;
        }
    }

    static ILoaderEngine getPictureLoader() {
        return sEngine;
    }

    static void loadPicture(@NonNull Context context, @NonNull MediaMeta mediaMeta, @NonNull ImageView imageView) {
        if (sEngine == null) {
            Log.e(TAG, "Loader.loadPicture -> please invoke Loader.setLoaderEngine first");
            return;
        }
        sEngine.loadPicture(context, mediaMeta, imageView);
    }

    static void loadGif(@NonNull Context context, @NonNull MediaMeta mediaMeta, @NonNull ImageView imageView) {
        if (sEngine == null) {
            Log.e(TAG, "Loader.loadPicture -> please invoke Loader.setLoaderEngine first");
            return;
        }
        sEngine.loadGif(context, mediaMeta, imageView);
    }

    static void loadVideo(@NonNull Context context, @NonNull MediaMeta mediaMeta, @NonNull ImageView imageView) {
        if (sEngine == null) {
            Log.e(TAG, "Loader.loadPicture -> please invoke Loader.setLoaderEngine first");
            return;
        }
        sEngine.loadVideoThumbnails(context, mediaMeta, imageView);
    }

}


================================================
FILE: lib-album/src/main/base/com/sharry/lib/album/MediaMeta.java
================================================
package com.sharry.lib.album;

import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * 媒体资源描述
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2019-09-02 14:01
 */
public class MediaMeta implements Parcelable {

    /**
     * 创建图片资源
     */
    public static MediaMeta createPicture(@NonNull Uri uri) {
        return create(uri, "", true);
    }

    /**
     * 创建视频资源
     */
    public static MediaMeta createVideo(@NonNull Uri uri) {
        return create(uri, "", false);
    }

    static MediaMeta create(@NonNull Uri uri, String filePath, boolean isPicture) {
        return new MediaMeta(uri, filePath, isPicture);
    }

    public static final Creator<MediaMeta> CREATOR = new Creator<MediaMeta>() {
        @Override
        public MediaMeta createFromParcel(Parcel in) {
            return new MediaMeta(in);
        }

        @Override
        public MediaMeta[] newArray(int size) {
            return new MediaMeta[size];
        }
    };

    protected MediaMeta(Parcel in) {
        contentUri = in.readParcelable(Uri.class.getClassLoader());
        path = in.readString();
        isPicture = in.readByte() != 0;
        size = in.readLong();
        date = in.readLong();
        duration = in.readLong();
        thumbnailPath = in.readString();
        mimeType = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(contentUri, flags);
        dest.writeString(path);
        dest.writeByte((byte) (isPicture ? 1 : 0));
        dest.writeLong(size);
        dest.writeLong(date);
        dest.writeLong(duration);
        dest.writeString(thumbnailPath);
        dest.writeString(mimeType);
    }

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

    /**
     * 文件的 URI
     * <p>
     * Android 10 以上, 只能够使用 URI 进行文件读写
     */
    @NonNull
    Uri contentUri;

    /**
     * 文件路径
     */
    @Deprecated
    String path;

    /**
     * 判断是否是图片
     */
    final boolean isPicture;

    /**
     * 文件大小
     */
    long size = 0;

    /**
     * 文件创建时间
     */
    long date = 0;

    /**
     * 时长(视频)
     * <p>
     * Unit ms
     */
    long duration = 0;

    /**
     * 视频缩略图
     */
    @Nullable
    String thumbnailPath;

    /**
     * 媒体文件类型
     */
    String mimeType;

    private MediaMeta(@NonNull Uri uri, @NonNull String filePath, boolean isPicture) {
        this.contentUri = uri;
        this.path = filePath;
        this.isPicture = isPicture;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        MediaMeta mediaMeta = (MediaMeta) o;
        return contentUri.equals(mediaMeta.contentUri);
    }

    @Override
    public int hashCode() {
        return contentUri.hashCode();
    }

    @Override
    public String toString() {
        return "MediaMeta{" +
                "contentUri='" + contentUri + '\'' + ", \n" +
                "path='" + path + '\'' + ", \n" +
                "isPicture=" + isPicture + ", \n" +
                "size=" + size + ", \n" +
                "date=" + date + ", \n" +
                "duration=" + duration + ", \n" +
                "thumbnailPath='" + thumbnailPath + '\'' + ", \n" +
                "mimeType='" + mimeType + '\'' + "\n" +
                '}';
    }

    @NonNull
    public Uri getContentUri() {
        return contentUri;
    }

    @NonNull
    @Deprecated
    public String getPath() {
        return path;
    }

    public boolean isPicture() {
        return isPicture;
    }

    public long getSize() {
        return size;
    }

    public long getDate() {
        return date;
    }

    public long getDuration() {
        return duration;
    }

    @Nullable
    public String getThumbnailPath() {
        return thumbnailPath;
    }

    public String getMimeType() {
        return mimeType;
    }

}


================================================
FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperCallback.java
================================================
package com.sharry.lib.album;

import androidx.annotation.NonNull;

/**
 * 图片裁剪的回调
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 4/28/2019 5:07 PM
 */
public interface CropperCallback {

    /**
     * 裁剪完成的回调
     */
    void onCropComplete(@NonNull MediaMeta meta);

    void onCropFailed();

}


================================================
FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperCallbackLambda.java
================================================
package com.sharry.lib.album;

import androidx.annotation.Nullable;

/**
 * 图片裁剪的回调
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 4/28/2019 5:07 PM
 */
public interface CropperCallbackLambda {

    void onCropped(@Nullable MediaMeta meta);

}


================================================
FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperConfig.java
================================================
package com.sharry.lib.album;

import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * 图片裁剪的相关参数
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.1
 * @since 2018/11/29 16:57
 */
public class CropperConfig implements Parcelable {

    protected CropperConfig(Parcel in) {
        originUri = in.readParcelable(Uri.class.getClassLoader());
        relativePath = in.readString();
        isCropCircle = in.readByte() != 0;
        authority = in.readString();
        aspectX = in.readInt();
        aspectY = in.readInt();
        outputX = in.readInt();
        outputY = in.readInt();
        destQuality = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(originUri, flags);
        dest.writeString(relativePath);
        dest.writeByte((byte) (isCropCircle ? 1 : 0));
        dest.writeString(authority);
        dest.writeInt(aspectX);
        dest.writeInt(aspectY);
        dest.writeInt(outputX);
        dest.writeInt(outputY);
        dest.writeInt(destQuality);
    }

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

    public static final Creator<CropperConfig> CREATOR = new Creator<CropperConfig>() {
        @Override
        public CropperConfig createFromParcel(Parcel in) {
            return new CropperConfig(in);
        }

        @Override
        public CropperConfig[] newArray(int size) {
            return new CropperConfig[size];
        }
    };

    public static Builder Builder() {
        return new Builder();
    }

    private Uri originUri;              // 需要裁剪的图片路径
    private String relativePath;        // 可用的输出目录
    private boolean isCropCircle;       // 是否为圆形裁剪
    private String authority;           // fileProvider 的 authority 属性, 用于 7.0 之后, 查找文件的 URI
    private int aspectX = 1;            // 方形 X 的比率
    private int aspectY = 1;            // 方形 X 的比率
    private int outputX = 500;          // 图像输出时的宽
    private int outputY = 500;          // 图像输出的高
    private int destQuality = 80;       // 裁剪后图片输出的质量

    private CropperConfig() {
    }

    public Uri getOriginUri() {
        return originUri;
    }

    public String getRelativePath() {
        return relativePath;
    }

    public boolean isCropCircle() {
        return isCropCircle;
    }

    public String getAuthority() {
        return authority;
    }

    public int getAspectX() {
        return aspectX;
    }

    public int getAspectY() {
        return aspectY;
    }

    public int getOutputX() {
        return outputX;
    }

    public int getOutputY() {
        return outputY;
    }

    public int getDestQuality() {
        return destQuality;
    }

    public Builder rebuild() {
        return new Builder(this);
    }

    /**
     * 构建 Config 对象
     */
    public static class Builder {

        private CropperConfig mConfig;

        private Builder() {
            mConfig = new CropperConfig();
        }

        private Builder(@NonNull CropperConfig config) {
            this.mConfig = config;
        }

        /**
         * 设置是否为圆形裁剪区域
         */
        public Builder setCropCircle(boolean isCropCircle) {
            this.mConfig.isCropCircle = isCropCircle;
            return this;
        }

        /**
         * 设置裁剪的尺寸
         */
        public Builder setCropSize(int width, int height) {
            this.mConfig.outputX = width;
            this.mConfig.outputY = height;
            return this;
        }

        /**
         * 设置裁剪的比例
         */
        public Builder setAspectSize(int x, int y) {
            this.mConfig.aspectX = x;
            this.mConfig.aspectY = y;
            return this;
        }

        /**
         * 设置 FileProvider 的路径, 7.0 以后用于查找 URI
         */
        public Builder setAuthority(@NonNull String authorities) {
            Preconditions.checkNotEmpty(authorities);
            mConfig.authority = authorities;
            return this;
        }

        /**
         * 设置需要裁剪的图片 URI 地址
         */
        public Builder setOriginUri(@NonNull Uri originUri) {
            this.mConfig.originUri = originUri;
            return this;
        }

        /**
         * 设置文件输出相对路径, 拍摄后的图片会生成在目录下
         * <p>
         * 绝对路径: "/storage/emulated/0/{@link android.os.Environment#DIRECTORY_PICTURES}/SAlbum"
         * 相对路径: "SAlbum"
         * <p>
         * 注:
         * Android 10 无法在外部存储卡随意创建文件, 因此会在对应的媒体目录下追加相对路径
         * 如: "/storage/emulated/0/" + {@link android.os.Environment#DIRECTORY_PICTURES} + "SAlbum"
         *
         * @param relativePath 若是传 null, 则会在 "/storage/emulated/0/"
         *                     + {@link android.os.Environment#DIRECTORY_PICTURES} 中创建
         */
        public Builder setRelativePath(@Nullable String relativePath) {
            this.mConfig.relativePath = relativePath;
            return this;
        }

        /**
         * 设置裁剪后压缩的质量
         */
        public Builder setCropQuality(int quality) {
            mConfig.destQuality = quality;
            return this;
        }

        @NonNull
        public CropperConfig build() {
            if (TextUtils.isEmpty(mConfig.authority)) {
                throw new UnsupportedOperationException("Please invoke setAuthority correct");
            }
            return mConfig;
        }
    }
}


================================================
FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperFragment.java
================================================
package com.sharry.lib.album;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.File;
import java.util.List;

/**
 * 调用系统裁剪工具
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 4/28/2019 4:53 PM
 */
public class CropperFragment extends Fragment {

    public static final String TAG = CropperFragment.class.getSimpleName();
    private static final int REQUEST_CODE_CROP = 446;
    public static final String INTENT_ACTION_START_CROP = "com.android.camera.action.CROP";

    /**
     * Get callback fragment from here.
     */
    @Nullable
    public static CropperFragment getInstance(@NonNull Activity bind) {
        if (ActivityStateUtil.isIllegalState(bind)) {
            return null;
        }
        CropperFragment callbackFragment = findFragmentFromActivity(bind);
        if (callbackFragment == null) {
            callbackFragment = new CropperFragment();
            FragmentManager fragmentManager = bind.getFragmentManager();
            fragmentManager.beginTransaction()
                    .add(callbackFragment, TAG)
                    .commitAllowingStateLoss();
            fragmentManager.executePendingTransactions();
        }
        return callbackFragment;
    }

    /**
     * 在 Activity 中通过 TAG 去寻找我们添加的 Fragment
     */
    private static CropperFragment findFragmentFromActivity(@NonNull Activity activity) {
        return (CropperFragment) activity.getFragmentManager().findFragmentByTag(TAG);
    }

    private File mTempFile;
    private Context mContext;
    private CropperConfig mConfig;
    private CropperCallback mCropperCallback;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mContext = context;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mContext = activity;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    /**
     * 开始裁剪
     */
    public void cropPicture(CropperConfig config, CropperCallback callback) {
        this.mConfig = config;
        this.mCropperCallback = callback;
        // Create temp file associated with crop function.
        mTempFile = FileUtil.createTempJpegFile(mContext);
        try {
            // Get URI associated with target file.
            Uri tempUri = FileUtil.getUriFromFile(mContext, config.getAuthority(), mTempFile);
            // Completion intent instance.
            Intent intent = new Intent(INTENT_ACTION_START_CROP);
            completion(intent, config, config.getOriginUri(), tempUri);
            // launch crop Activity
            startActivityForResult(intent, REQUEST_CODE_CROP);
        } catch (Throwable e) {
            mCropperCallback.onCropFailed();
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (mCropperCallback == null) {
            return;
        }
        if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_CROP) {
            try {
                // 创建最终的目标文件, 将图片从临时文件压缩到指定的目录
                if (VersionUtil.isQ()) {
                    Uri uri = FileUtil.createJpegPendingItem(mContext, mConfig.getRelativePath());
                    ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(uri, "w");
                    CompressUtil.doCompress(mTempFile.getAbsolutePath(), pfd.getFileDescriptor(), mConfig.getDestQuality());
                    FileUtil.publishPendingItem(mContext, uri);
                    MediaMeta mediaMeta = MediaMeta.create(uri, FileUtil.getImagePath(mContext, uri), true);
                    mCropperCallback.onCropComplete(mediaMeta);
                } else {
                    File file = FileUtil.createJpegFile(mContext, mConfig.getRelativePath());
                    Uri uri = FileUtil.getUriFromFile(mContext, mConfig.getAuthority(), file);
                    ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(uri, "w");
                    CompressUtil.doCompress(mTempFile.getAbsolutePath(), pfd.getFileDescriptor(), mConfig.getDestQuality());
                    FileUtil.notifyMediaStore(mContext, file.getAbsolutePath());
                    MediaMeta mediaMeta = MediaMeta.create(uri, file.getAbsolutePath(), true);
                    mCropperCallback.onCropComplete(mediaMeta);
                }
            } catch (Exception e) {
                mCropperCallback.onCropFailed();
            } finally {
                if (mTempFile != null) {
                    mTempFile.delete();
                }
            }
        } else {
            mCropperCallback.onCropFailed();
        }
    }

    private void completion(Intent intent, CropperConfig config, Uri originUri, Uri tempUri) {
        // 可以选择图片类型, 如果是*表明所有类型的图片
        intent.setDataAndType(originUri, "image/*");
        // 设置可裁剪状态
        intent.putExtra("crop", true);
        // 裁剪时是否保留图片的比例, 这里的比例是1:1
        intent.putExtra("scale", config.getAspectX() == config.getAspectY());
        // X方向上的比例
        intent.putExtra("aspectX", config.getAspectX());
        // Y方向上的比例
        intent.putExtra("aspectY", config.getAspectY());
        // 裁剪区域的宽
        intent.putExtra("outputX", config.getOutputX());
        // 裁剪区域的宽
        intent.putExtra("outputY", config.getOutputY());
        // 是否将数据保留在Bitmap中返回, 返回的缩略图效果模糊
        intent.putExtra("return-data", false);
        // 设置输出的格式
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        // 裁剪后的保存路径, 这里的 URI 不需要区分
        intent.putExtra(MediaStore.EXTRA_OUTPUT, tempUri);
        // 不启用人脸识别
        intent.putExtra("noFaceDetection", true);
        // 安卓 6.0 以上版本添加读写权限请求
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            // 将存储图片的 uri 读写权限授权给剪裁工具应用
            List<ResolveInfo> resInfoList = mContext.getPackageManager()
                    .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
            for (ResolveInfo resolveInfo : resInfoList) {
                String packageName = resolveInfo.activityInfo.packageName;
                int modeFlags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                        | Intent.FLAG_GRANT_READ_URI_PERMISSION;
                getActivity().grantUriPermission(packageName, originUri, modeFlags);
                getActivity().grantUriPermission(packageName, tempUri, modeFlags);
            }
        }
    }

}


================================================
FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperManager.java
================================================
package com.sharry.lib.album;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;

/**
 * 图片裁剪的入口
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 4/28/2019 5:09 PM
 */
public class CropperManager {

    private static final String TAG = CropperManager.class.getSimpleName();
    private static String[] sPermissions = {
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
    };

    public static CropperManager with(@NonNull Context context) {
        if (context instanceof Activity) {
            Activity activity = (Activity) context;
            return new CropperManager(activity);
        } else {
            throw new IllegalArgumentException(TAG + ".with -> Context can not cast to Activity");
        }
    }

    private Activity mBind;
    private CropperConfig mConfig;

    private CropperManager(Activity activity) {
        this.mBind = activity;
    }

    /**
     * 设置配置属性
     */
    public CropperManager setConfig(@NonNull CropperConfig config) {
        this.mConfig = Preconditions.checkNotNull(config, "Please ensure config not null!");
        return this;
    }

    /**
     * 裁剪图片
     */
    public void crop(@NonNull final CropperCallbackLambda callback) {
        crop(new CropperCallback() {
            @Override
            public void onCropComplete(@NonNull MediaMeta meta) {
                callback.onCropped(meta);
            }

            @Override
            public void onCropFailed() {
                callback.onCropped(null);
            }
        });
    }

    /**
     * 裁剪图片
     */
    public void crop(@NonNull final CropperCallback callback) {
        Preconditions.checkNotNull(callback, "Please ensure callback not null!");
        Preconditions.checkNotNull(mConfig, "Please ensure setConfig correct!");
        PermissionsHelper.with(mBind)
                .request(sPermissions)
                .execute(new PermissionsCallback() {
                    @Override
                    public void onResult(boolean granted) {
                        if (granted) {
                            cropActual(callback);
                        }
                    }
                });
    }

    /**
     * 裁剪图片
     */
    private void cropActual(@NonNull final CropperCallback callback) {
        // 若未指定目的路径, 则在系统相册的路径下创建图片文件
        if (mConfig.getOriginUri() == null) {
            throw new UnsupportedOperationException(TAG + ".takeActual -> Please ensure crop " +
                    "target uri is valuable.");
        }
        // 指定默认, FileProvider 的 authority
        if (TextUtils.isEmpty(mConfig.getAuthority())) {
            throw new UnsupportedOperationException(TAG + "Please ensure u set FileProvider authority correct!.");
        }
        // 执行回调
        CropperFragment callbackFragment = CropperFragment.getInstance(mBind);
        if (callbackFragment == null) {
            Log.e(TAG, "Launch crop activity failed.");
            return;
        }
        callbackFragment.cropPicture(mConfig, callback);
    }

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/FolderAdapter.java
================================================
package com.sharry.lib.album;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

/**
 * 图片文件夹的 Adapter
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/9/22 15:02
 */
class FolderAdapter extends RecyclerView.Adapter<FolderAdapter.ViewHolder> {

    final Context context;
    final List<FolderModel> data;
    final AdapterInteraction callback;

    FolderAdapter(Context context, List<FolderModel> data) {
        if (context instanceof AdapterInteraction) {
            this.callback = (AdapterInteraction) context;
        } else {
            throw new IllegalArgumentException(context + "must implements " +
                    FolderAdapter.class.getSimpleName() + ".Interaction");
        }
        this.context = context;
        this.data = data;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.lib_album_recycle_item_folder, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
        FolderModel folder = data.get(holder.getAdapterPosition());
        if (folder == null || folder.getMetas() == null || folder.getMetas().isEmpty()) {
            return;
        }
        MediaMeta firstMeta = folder.getMetas().get(0);
        if (firstMeta.isPicture) {
            Loader.loadPicture(context, firstMeta, holder.ivPreview);
        } else {
            Loader.loadVideo(context, firstMeta, holder.ivPreview);
        }
        holder.tvFolderName.setText(folder.getName());
    }

    @Override
    public int getItemCount() {
        return data.size();
    }

    /**
     * Communicate with Activity.
     */
    public interface AdapterInteraction {

        void onFolderChecked(int position);

    }

    class ViewHolder extends RecyclerView.ViewHolder {
        private ImageView ivPreview;
        private TextView tvFolderName;

        private ViewHolder(View itemView) {
            super(itemView);
            tvFolderName = itemView.findViewById(R.id.tv_folder_name);
            ivPreview = itemView.findViewById(R.id.iv_preview);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    callback.onFolderChecked(getAdapterPosition());
                }
            });
        }
    }

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/FolderModel.java
================================================
package com.sharry.lib.album;

import androidx.annotation.NonNull;

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

/**
 * Describe pictures that in the same folder.
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/8/31 22:29
 */
class FolderModel {

    private final String name;
    private final List<MediaMeta> metas = new ArrayList<>();

    FolderModel(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    List<MediaMeta> getMetas() {
        return metas;
    }

    synchronized void addMeta(@NonNull MediaMeta meta) {
        int insertIndex = 0;
        for (; insertIndex < metas.size(); insertIndex++) {
            if (metas.get(insertIndex).date < meta.date) {
                break;
            }
        }
        metas.add(insertIndex, meta);
    }

}

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerActivity.java
================================================
package com.sharry.lib.album;

import android.app.Activity;
import android.app.Fragment;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.sharry.lib.album.toolbar.SToolbar;
import com.sharry.lib.album.toolbar.TextViewOptions;

import java.util.ArrayList;

/**
 * 图片选择器的 Activity
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.3
 * @since 2018/9/1 10:17
 */
public class PickerActivity extends AppCompatActivity implements PickerContract.IView,
        PickerAdapter.Interaction,
        FolderAdapter.AdapterInteraction,
        View.OnClickListener {

    /**
     * Constants.
     */
    public static final int REQUEST_CODE = 267;
    public static final String RESULT_EXTRA_PICKED_PICTURES = "result_intent_extra_picked_pictures";
    private static final String EXTRA_CONFIG = "start_intent_extra_config";

    /**
     * U can launch PickerActivity from here.
     * If U picked success, it will return picked data, U can got it like
     * {@code ArrayList<String> paths = data.getStringArrayListExtra(PickerActivity.RESULT_EXTRA_PICKED_PICTURES)}
     *
     * @param from     The Activity that request launch PickerActivity.
     * @param resultTo Result data will return to this instance.
     * @param config   Launch PickerActivity required data.
     */
    public static void launchActivityForResult(Activity from, Fragment resultTo, PickerConfig config) {
        Intent intent = new Intent(from, PickerActivity.class);
        intent.putExtra(PickerActivity.EXTRA_CONFIG, config);
        resultTo.startActivityForResult(intent, REQUEST_CODE);
    }

    /**
     * Presenter associated with this Activity.
     */
    private PickerContract.IPresenter mPresenter;

    /**
     * Views
     */
    private SToolbar mToolbar;
    private ProgressBar mProgressBar;
    private TextView mTvToolbarFolderName;
    private TextView mTvToolbarEnsure;
    private RecyclerView mRvPicker;
    private ViewGroup mMenuNavContainer;
    private ImageView mIvNavIndicator;
    private TextView mTvFolderName;
    private TextView mTvPreview;
    private RecyclerView mRvFolders;
    private FloatingActionButton mFab;

    /**
     * CoordinatorLayout behaviors.
     */
    private BottomSheetBehavior mBottomMenuBehavior;
    private PicturePickerFabBehavior mFabBehavior;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.lib_album_activity_picker);
        initTitle();
        initViews();
        initPresenter();
        registerLocalBroadcast();
    }

    protected void initTitle() {
        // 初始化视图
        mToolbar = findViewById(R.id.toolbar);
        // 设置标题文本
        mToolbar.setTitleText(getString(R.string.lib_album_picker_all_picture));
        mTvToolbarFolderName = mToolbar.getTitleText();
        // 添加图片确认按钮
        mToolbar.addRightMenuText(
                TextViewOptions.Builder()
                        .setText(getString(R.string.lib_album_picker_ensure))
                        .setTextSize(15)
                        .setListener(this)
                        .build()
        );
        mTvToolbarEnsure = mToolbar.getRightMenuView(0);
    }

    protected void initViews() {
        // Pictures recycler view.
        mRvPicker = findViewById(R.id.rv_picker);
        mRvPicker.addItemDecoration(new RecyclerView.ItemDecoration() {
            @Override
            public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                super.onDraw(c, parent, state);
                mPresenter.handleRecycleViewDraw(parent);
            }
        });
        // Bottom navigation menu.
        mMenuNavContainer = findViewById(R.id.rv_menu_nav_container);
        mIvNavIndicator = findViewById(R.id.iv_nav_indicator);
        mTvFolderName = findViewById(R.id.tv_folder_name);
        mTvPreview = findViewById(R.id.tv_preview);
        mRvFolders = findViewById(R.id.recycle_folders);
        mTvFolderName.setOnClickListener(this);
        mTvPreview.setOnClickListener(this);
        mRvFolders.setLayoutManager(new LinearLayoutManager(this));
        mRvFolders.setHasFixedSize(true);
        mBottomMenuBehavior = BottomSheetBehavior.from(findViewById(R.id.ll_bottom_menu));
        mBottomMenuBehavior.setBottomSheetCallback(new BottomMenuNavigationCallback());
        // Floating action bar.
        mFab = findViewById(R.id.fab);
        mFab.setOnClickListener(this);
        mFabBehavior = PicturePickerFabBehavior.from(mFab);
        // Progress bar.
        mProgressBar = findViewById(R.id.progress_bar);
    }

    protected void initPresenter() {
        PickerConfig config = getIntent().getParcelableExtra(EXTRA_CONFIG);
        if (config != null) {
            mPresenter = new PickerPresenter(this, config);
        }
    }

    private final BroadcastReceiver mBrPickedSetChanged = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (mPresenter != null) {
                MediaMeta mediaMeta = intent.getParcelableExtra(WatcherActivity.BROADCAST_EXTRA_DATA);
                mPresenter.handlePickedSetChanged(mediaMeta);
            }
        }
    };

    private final BroadcastReceiver mBrPickedSetEnsure = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            mPresenter.handleEnsureClicked();
        }
    };

    private void registerLocalBroadcast() {
        LocalBroadcastManager.getInstance(this)
                .registerReceiver(
                        mBrPickedSetChanged,
                        new IntentFilter(WatcherActivity.BROADCAST_PICKED_SET_CHANGED)
                );
        LocalBroadcastManager.getInstance(this)
                .registerReceiver(
                        mBrPickedSetEnsure,
                        new IntentFilter(WatcherActivity.BROADCAST_PICKED_SET_ENSURE)
                );
    }

    @Override
    public void onBackPressed() {
        if (BottomSheetBehavior.STATE_COLLAPSED != mBottomMenuBehavior.getState()) {
            mBottomMenuBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    protected void onDestroy() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mBrPickedSetChanged);
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mBrPickedSetEnsure);
        mPresenter.handleViewDestroy();
        super.onDestroy();
    }

    //////////////////////////////////////////////PickerContract.IView/////////////////////////////////////////////////

    @Override
    public void setToolbarBackgroundColor(int color) {
        mToolbar.setBackgroundColor(color);
    }

    @Override
    public void setToolbarBackgroundDrawable(int drawableId) {
        mToolbar.setBackgroundDrawableRes(drawableId);
    }

    @Override
    public void setToolbarScrollable(boolean isScrollable) {
        if (isScrollable) {
            AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
            params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
                    | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
                    | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
            mToolbar.setLayoutParams(params);
        }
    }

    @Override
    public void setBackgroundColor(int color) {
        mRvPicker.setBackgroundColor(color);
    }

    @Override
    public void setSpanCount(int spanCount) {
        mRvPicker.setLayoutManager(new GridLayoutManager(this, spanCount));
    }

    @Override
    public void setPickerAdapter(@NonNull PickerConfig config,
                                 @NonNull ArrayList<MediaMeta> metas,
                                 @NonNull ArrayList<MediaMeta> userPickedMetas) {
        mRvPicker.setAdapter(new PickerAdapter(this, config,
                metas, userPickedMetas));
    }

    @Override
    public void setFolderAdapter(@NonNull ArrayList<FolderModel> folders) {
        mRvFolders.setAdapter(new FolderAdapter(this, folders));
    }

    @Override
    public void setFabColor(int color) {
        mFab.setBackgroundTintList(ColorStateList.valueOf(color));
    }

    @Override
    public void setProgressBarVisible(boolean visible) {
        mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
    }

    @Override
    public void setFabVisible(boolean isVisible) {
        if (isVisible) {
            mFab.show();
        } else {
            mFab.hide();
        }
    }

    @Override
    public void setPictureFolderText(@NonNull String folderName) {
        // 更新文件夹名称
        mTvFolderName.setText(folderName);
        mTvToolbarFolderName.setText(folderName);
    }

    @Override
    public void setToolbarEnsureText(@NonNull CharSequence content) {
        mTvToolbarEnsure.setText(content);
    }

    @Override
    public void setPreviewText(@NonNull CharSequence content) {
        mTvPreview.setText(content);
    }

    @Override
    public void notifyDisplaySetItemChanged(int changedIndex) {
        RecyclerView.Adapter adapter;
        if ((adapter = mRvPicker.getAdapter()) != null) {
            adapter.notifyItemChanged(changedIndex);
        }
    }

    @Override
    public void notifyDisplaySetChanged() {
        RecyclerView.Adapter adapter;
        if ((adapter = mRvPicker.getAdapter()) != null) {
            adapter.notifyDataSetChanged();
        }
    }

    @Override
    public void notifyNewMetaInsertToFirst() {
        RecyclerView.Adapter adapter;
        if ((adapter = mRvPicker.getAdapter()) != null) {
            adapter.notifyItemInserted(1);
        }
    }

    @Override
    public void notifyFolderDataSetChanged() {
        RecyclerView.Adapter adapter;
        if ((adapter = mRvFolders.getAdapter()) != null) {
            adapter.notifyDataSetChanged();
        }
    }

    @Override
    public void showMsg(@NonNull String msg) {
        Snackbar.make(mFab, msg, Snackbar.LENGTH_LONG).show();
    }

    @Override
    public void setResultAndFinish(@NonNull ArrayList<MediaMeta> pickedPaths) {
        Intent intent = new Intent();
        intent.putExtra(PickerActivity.RESULT_EXTRA_PICKED_PICTURES, pickedPaths);
        setResult(Activity.RESULT_OK, intent);
        finish();
    }

    //////////////////////////////////////////////PickerAdapter.Interaction/////////////////////////////////////////////////

    @Override
    public void onCameraClicked() {
        mPresenter.handleCameraClicked();
    }

    @Override
    public void onPictureClicked(@NonNull View itemView, @NonNull Uri uri, int position) {
        mPresenter.handlePictureClicked(position, itemView);
    }

    @Override
    public boolean onPictureChecked(@NonNull MediaMeta checkedMeta) {
        return mPresenter.handlePictureChecked(checkedMeta);
    }

    @Override
    public void onPictureRemoved(@NonNull MediaMeta removedMeta) {
        mPresenter.handlePictureUnchecked(removedMeta);
    }

    //////////////////////////////////////////////FolderAdapter.Interaction/////////////////////////////////////////////////

    @Override
    public void onFolderChecked(int position) {
        mPresenter.handleFolderChecked(position);
        mBottomMenuBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    }

    //////////////////////////////////////////////View.OnClickListener/////////////////////////////////////////////////

    @Override
    public void onClick(View v) {
        // 底部菜单按钮
        if (v.getId() == R.id.tv_folder_name) {
            mBottomMenuBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        }
        // 预览按钮
        else if (v.getId() == R.id.tv_preview) {
            mPresenter.handlePreviewClicked();
        }
        // 确认按钮
        else if (v == mTvToolbarEnsure || v.getId() == R.id.fab) {
            mPresenter.handleEnsureClicked();
        }
    }

    /**
     * Callback associated with bottom menu navigation bar.
     * Method will be invoked when menu scrolled.
     */
    private class BottomMenuNavigationCallback extends BottomSheetBehavior.BottomSheetCallback {

        private final Drawable indicatorDrawable;
        private final int bgCollapsedColor;
        private final int bgExpandColor;
        private final int textCollapsedColor;
        private final int textExpandColor;

        BottomMenuNavigationCallback() {
            indicatorDrawable = mIvNavIndicator.getDrawable();
            bgCollapsedColor = ContextCompat.getColor(PickerActivity.this,
                    R.color.lib_album_picker_bottom_menu_nav_bg_collapsed_color);
            bgExpandColor = ContextCompat.getColor(PickerActivity.this,
                    R.color.lib_album_picker_bottom_menu_navi_bg_expand_color);
            textCollapsedColor = ContextCompat.getColor(PickerActivity.this,
                    R.color.lib_album_picker_bottom_menu_nav_text_collapsed_color);
            textExpandColor = ContextCompat.getColor(PickerActivity.this,
                    R.color.lib_album_picker_bottom_menu_navi_text_expand_color);
        }

        @Override
        public void onStateChanged(@NonNull View view, int state) {
            mFabBehavior.setBehaviorValid(BottomSheetBehavior.STATE_COLLAPSED == state);
        }

        @Override
        public void onSlide(@NonNull View view, float fraction) {
            // Get background color associate with the bottom menu navigation bar.
            int bgColor = ColorUtil.gradualChanged(fraction, bgCollapsedColor, bgExpandColor);
            mMenuNavContainer.setBackgroundColor(bgColor);
            // Get text color associate with the bottom menu navigation bar.
            int textColor = ColorUtil.gradualChanged(fraction, textCollapsedColor, textExpandColor);
            // Set text drawable color before set text color with the purpose of decrease view draw.
            if (VersionUtil.isLollipop()) {
                indicatorDrawable.setTint(textColor);
            }
            // Set texts colors associate with the bottom menu.
            mTvFolderName.setTextColor(textColor);
            mTvPreview.setTextColor(textColor);
        }
    }

}

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerAdapter.java
================================================
package com.sharry.lib.album;

import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

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

/**
 * Adapter associated with PicturePicker.
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.3
 * @since 2018/9/1 10:19
 */
class PickerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int ITEM_TYPE_PICTURE = 838;
    private static final int ITEM_TYPE_CAMERA_HEADER = 347;
    private static final int ITEM_TYPE_VIDEO = 664;

    private final Context mContext;
    private final PickerConfig mConfig;
    private final List<MediaMeta> mDataSet;
    private final List<MediaMeta> mPickedSet;
    private final Interaction mInteraction;
    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
    private final Runnable mRefreshIndicatorRunnable = new Runnable() {
        @Override
        public void run() {
            notifyDataSetChanged();
        }
    };

    PickerAdapter(Context context,
                  PickerConfig config,
                  ArrayList<MediaMeta> dataSet,
                  ArrayList<MediaMeta> pickedSet) {
        if (context instanceof Interaction) {
            this.mInteraction = (Interaction) context;
        } else {
            throw new IllegalArgumentException(context + "must implements " +
                    PickerAdapter.class.getSimpleName() + ".Interaction");
        }
        this.mContext = context;
        this.mConfig = config;
        this.mDataSet = dataSet;
        this.mPickedSet = pickedSet;
    }

    @Override
    public int getItemViewType(int position) {
        if (mConfig.isCameraSupport() && position == 0) {
            return ITEM_TYPE_CAMERA_HEADER;
        }
        int relativePosition = mConfig.isCameraSupport() ? position - 1 : position;
        MediaMeta meta = mDataSet.get(relativePosition);
        int result;
        if (meta == null || meta.isPicture) {
            result = ITEM_TYPE_PICTURE;
        } else {
            result = ITEM_TYPE_VIDEO;
        }
        return result;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder vh;
        switch (viewType) {
            case ITEM_TYPE_CAMERA_HEADER:
                vh = new CameraHeaderHolder(parent);
                break;
            case ITEM_TYPE_VIDEO:
                vh = new VideoViewHolder(parent);
                break;
            case ITEM_TYPE_PICTURE:
            default:
                vh = new PictureViewHolder(parent);
                break;
        }
        return vh;
    }

    @Override
    public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof CameraHeaderHolder) {
            // nothing.
        } else if (holder instanceof PictureViewHolder) {
            int relativePosition = mConfig.isCameraSupport() ? position - 1 : position;
            if (relativePosition < 0) {
                return;
            }
            final MediaMeta meta = mDataSet.get(relativePosition);
            if (meta == null) {
                return;
            }
            bindPictureItem((PictureViewHolder) holder, meta);
        } else if (holder instanceof VideoViewHolder) {
            int relativePosition = mConfig.isCameraSupport() ? position - 1 : position;
            if (relativePosition < 0) {
                return;
            }
            final MediaMeta meta = mDataSet.get(relativePosition);
            if (meta == null) {
                return;
            }
            bindVideoItem((VideoViewHolder) holder, meta);
        } else {
            // nothing.
        }
    }

    @Override
    public int getItemCount() {
        return mDataSet.size() + (mConfig.isCameraSupport() ? 1 : 0);
    }

    /**
     * 绑定图像视图
     */
    private void bindPictureItem(final PictureViewHolder holder, final MediaMeta meta) {
        holder.ivPicture.setBackgroundColor(mConfig.getPickerItemBackgroundColor());
        holder.ivPicture.setScaleType(ImageView.ScaleType.CENTER_CROP);
        holder.ivGifTag.setVisibility(Constants.MIME_TYPE_GIF.equals(meta.mimeType) ? View.VISIBLE : View.GONE);
        Loader.loadPicture(mContext, meta, holder.ivPicture);
        // 判断当前 uri 是否被选中了
        final int index = mPickedSet.indexOf(meta);
        // 设置点击监听
        holder.checkIndicator.setVisibility(View.VISIBLE);
        holder.checkIndicator.setCheckedWithoutAnimator(index != -1);
        holder.checkIndicator.setText(String.valueOf(index + 1));
    }

    /**
     * 绑定视频视图
     */
    private void bindVideoItem(final VideoViewHolder holder, final MediaMeta meta) {
        holder.ivPicture.setBackgroundColor(mConfig.getPickerItemBackgroundColor());
        holder.ivPicture.setScaleType(ImageView.ScaleType.CENTER_CROP);
        // 加载视频第一帧
        Loader.loadVideo(mContext, meta, holder.ivPicture);
        // 判断当前 uri 是否被选中了
        final int index = mPickedSet.indexOf(meta);
        // 设置点击监听
        holder.checkIndicator.setVisibility(View.VISIBLE);
        holder.checkIndicator.setCheckedWithoutAnimator(index != -1);
        holder.checkIndicator.setText(String.valueOf(index + 1));
        // 设置时长
        holder.tvDuration.setText(DateUtil.format(meta.duration));
    }

    /**
     * 通知选中图片的角标变更
     */
    private void notifyCheckedIndicatorChanged() {
        mMainThreadHandler.postDelayed(mRefreshIndicatorRunnable, 300);
    }

    /**
     * Camera header item view holder.
     */
    class CameraHeaderHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        CameraHeaderHolder(ViewGroup parent) {
            super(LayoutInflater.from(parent.getContext()).inflate(
                    R.layout.lib_album_recycle_item_header_camera,
                    parent,
                    false
            ));
            // 将 ItemView 的高度修正为宽度 parent 的宽度的三分之一
            int itemSize = (parent.getMeasuredWidth() - parent.getPaddingLeft()
                    - parent.getPaddingRight()) / mConfig.getSpanCount();
            ViewGroup.LayoutParams itemParams = itemView.getLayoutParams();
            itemParams.height = itemSize;
            itemView.setLayoutParams(itemParams);
            // 注入点击事件
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            mInteraction.onCameraClicked();
        }
    }

    /**
     * Picture item view holder
     */
    class PictureViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        final ImageView ivPicture;
        final CheckedIndicatorView checkIndicator;
        final ImageView ivGifTag;
        final Runnable pictureClickedRunnable = new Runnable() {
            @Override
            public void run() {
                performPictureClicked();
            }
        };

        PictureViewHolder(ViewGroup parent) {
            super(
                    LayoutInflater.from(parent.getContext())
                            .inflate(R.layout.lib_album_recycle_item_picture, parent, false)
            );
            // Initialize ivPicture.
            ivPicture = itemView.findViewById(R.id.iv_picture);
            ivPicture.setOnClickListener(this);
            // Initialize ivGifTag
            ivGifTag = itemView.findViewById(R.id.iv_gif_tag);
            // Initialize checkIndicator.
            checkIndicator = itemView.findViewById(R.id.check_indicator);
            checkIndicator.setTextColor(mConfig.getIndicatorTextColor());
            checkIndicator.setSolidColor(mConfig.getIndicatorSolidColor());
            checkIndicator.setBorderColor(
                    mConfig.getIndicatorBorderCheckedColor(),
                    mConfig.getIndicatorBorderUncheckedColor()
            );
            checkIndicator.setOnClickListener(this);
            adjustItemView(parent);
        }

        @Override
        public void onClick(View v) {
            if (ivPicture == v) {
                // 延时 100 mm , 等待水波纹动画结束
                mMainThreadHandler.postDelayed(pictureClickedRunnable, 100);
            } else if (checkIndicator == v) {
                performCheckIndicatorClicked();
            }
        }

        private void adjustItemView(ViewGroup parent) {
            // 将 ItemView 的高度修正为宽度 parent 的宽度的三分之一
            int itemSize = (parent.getMeasuredWidth() - parent.getPaddingLeft()
                    - parent.getPaddingRight()) / mConfig.getSpanCount();
            ViewGroup.LayoutParams itemParams = itemView.getLayoutParams();
            itemParams.height = itemSize;
            itemView.setLayoutParams(itemParams);
            // 设置指示器的宽高为 ItemView 的五分之一
            int indicatorSize = itemSize / 5;
            ViewGroup.MarginLayoutParams indicatorParams =
                    (ViewGroup.MarginLayoutParams) checkIndicator.getLayoutParams();
            // 动态调整大小
            indicatorParams.width = indicatorSize;
            indicatorParams.height = indicatorSize;
            // 动态调整 Margin
            indicatorParams.rightMargin = indicatorSize / 5;
            indicatorParams.topMargin = indicatorSize / 5;
            checkIndicator.setLayoutParams(indicatorParams);
            // 设置指示器的文本尺寸为指示器宽高的二分之一
            checkIndicator.setTextSize(TypedValue.COMPLEX_UNIT_PX, indicatorSize / 2);
        }

        private void performPictureClicked() {
            int relativePosition = mConfig.isCameraSupport() ? getAdapterPosition() - 1 : getAdapterPosition();
            if (relativePosition < 0) {
                return;
            }
            mInteraction.onPictureClicked(itemView, mDataSet.get(relativePosition).contentUri, relativePosition);
        }

        private void performCheckIndicatorClicked() {
            // 获取当前点击图片的 path
            int relativePosition = mConfig.isCameraSupport() ? getAdapterPosition() - 1 : getAdapterPosition();
            if (relativePosition < 0) {
                return;
            }
            MediaMeta meta = mDataSet.get(relativePosition);
            // Checked-> Unchecked
            if (checkIndicator.isChecked()) {
                // 移除选中数据与状态
                mInteraction.onPictureRemoved(meta);
                checkIndicator.setChecked(false);
                // 需要延时的更新索引角标
                notifyCheckedIndicatorChanged();
            }
            // Unchecked -> Checked
            else {
                // 判断是否达到选择上限
                checkIndicator.setChecked(mInteraction.onPictureChecked(meta));
                // 设置文本
                checkIndicator.setText(String.valueOf(mPickedSet.size()));
            }
        }

    }

    /**
     * Video item view holder
     */
    class VideoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        final ImageView ivPicture;
        final CheckedIndicatorView checkIndicator;
        final TextView tvDuration;
        final Runnable pictureClickedRunnable = new Runnable() {
            @Override
            public void run() {
                performPictureClicked();
            }
        };

        VideoViewHolder(ViewGroup parent) {
            super(
                    LayoutInflater.from(parent.getContext()).inflate(
                            R.layout.lib_album_recycle_item_video,
                            parent,
                            false
                    )
            );
            // Initialize ivPicture.
            ivPicture = itemView.findViewById(R.id.iv_picture);
            ivPicture.setOnClickListener(this);
            // Initialize checkIndicator.
            checkIndicator = itemView.findViewById(R.id.check_indicator);
            checkIndicator.setTextColor(mConfig.getIndicatorTextColor());
            checkIndicator.setSolidColor(mConfig.getIndicatorSolidColor());
            checkIndicator.setBorderColor(
                    mConfig.getIndicatorBorderCheckedColor(),
                    mConfig.getIndicatorBorderUncheckedColor()
            );
            checkIndicator.setOnClickListener(this);
            // Initialize tvDuration
            tvDuration = itemView.findViewById(R.id.tv_duration);
            // adjust.
            adjustItemView(parent);
        }

        @Override
        public void onClick(View v) {
            if (ivPicture == v) {
                // 延时 100 mm , 等待水波纹动画结束
                mMainThreadHandler.postDelayed(pictureClickedRunnable, 100);
            } else if (checkIndicator == v) {
                performCheckIndicatorClicked();
            }
        }

        private void adjustItemView(ViewGroup parent) {
            // 将 ItemView 的高度修正为宽度 parent 的宽度的三分之一
            int itemSize = (parent.getMeasuredWidth() - parent.getPaddingLeft()
                    - parent.getPaddingRight()) / mConfig.getSpanCount();
            ViewGroup.LayoutParams itemParams = itemView.getLayoutParams();
            itemParams.height = itemSize;
            itemView.setLayoutParams(itemParams);
            // 设置指示器的宽高为 ItemView 的五分之一
            int indicatorSize = itemSize / 5;
            ViewGroup.MarginLayoutParams indicatorParams =
                    (ViewGroup.MarginLayoutParams) checkIndicator.getLayoutParams();
            // 动态调整大小
            indicatorParams.width = indicatorSize;
            indicatorParams.height = indicatorSize;
            // 动态调整 Margin
            indicatorParams.rightMargin = indicatorSize / 5;
            indicatorParams.topMargin = indicatorSize / 5;
            checkIndicator.setLayoutParams(indicatorParams);
            // 设置指示器的文本尺寸为指示器宽高的二分之一
            checkIndicator.setTextSize(TypedValue.COMPLEX_UNIT_PX, indicatorSize / 2);
        }

        private void performPictureClicked() {
            int relativePosition = mConfig.isCameraSupport() ? getAdapterPosition() - 1 : getAdapterPosition();
            if (relativePosition < 0) {
                return;
            }
            mInteraction.onPictureClicked(itemView, mDataSet.get(relativePosition).contentUri, relativePosition);
        }

        private void performCheckIndicatorClicked() {
            // 获取当前点击图片的 path
            int relativePosition = mConfig.isCameraSupport() ? getAdapterPosition() - 1 : getAdapterPosition();
            if (relativePosition < 0) {
                return;
            }
            MediaMeta meta = mDataSet.get(relativePosition);
            // Checked-> Unchecked
            if (checkIndicator.isChecked()) {
                // 移除选中数据与状态
                mInteraction.onPictureRemoved(meta);
                checkIndicator.setChecked(false);
                // 需要延时的更新索引角标
                notifyCheckedIndicatorChanged();
            }
            // Unchecked -> Checked
            else {
                // 判断是否达到选择上限
                checkIndicator.setChecked(mInteraction.onPictureChecked(meta));
                // 设置文本
                checkIndicator.setText(String.valueOf(mPickedSet.size()));
            }
        }

    }

    /**
     * Communicate with Activity.
     */
    interface Interaction {

        void onCameraClicked();

        void onPictureClicked(@NonNull View itemView, @NonNull Uri uri, int position);

        boolean onPictureChecked(@NonNull MediaMeta checkedMeta);

        void onPictureRemoved(@NonNull MediaMeta removedMeta);
    }

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerCallback.java
================================================
package com.sharry.lib.album;

import androidx.annotation.NonNull;

import java.util.ArrayList;

/**
 * 图片选择器的回调
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 4/28/2019 5:03 PM
 */
public interface PickerCallback {

    /**
     * 获取选中集合
     *
     * @param userPickedSet 用户选中的集合
     */
    void onPickedComplete(@NonNull ArrayList<MediaMeta> userPickedSet);

    /**
     * 未进行图片选取
     */
    void onPickedFailed();

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerCallbackLambda.java
================================================
package com.sharry.lib.album;

import androidx.annotation.Nullable;

import java.util.ArrayList;

/**
 * 图片选择器的回调
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 4/28/2019 5:03 PM
 */
public interface PickerCallbackLambda {

    /**
     * 获取选中集合
     *
     * @param userPickedSet 用户选中的集合
     */
    void onPicked(@Nullable ArrayList<MediaMeta> userPickedSet);

}

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerConfig.java
================================================
package com.sharry.lib.album;

import android.graphics.Color;
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;

/**
 * 图片选择器的配置属性类
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.1
 * @since 2018/11/29 17:07
 */
public class PickerConfig implements Parcelable {

    static final int INVALIDATE_VALUE = -1;
    static final int COLOR_DEFAULT = Color.parseColor("#ff64b6f6");

    protected PickerConfig(Parcel in) {
        userPickedSet = in.createTypedArrayList(MediaMeta.CREATOR);
        threshold = in.readInt();
        spanCount = in.readInt();
        toolbarBkgColor = in.readInt();
        toolbarBkgDrawableResId = in.readInt();
        pickerBackgroundColor = in.readInt();
        pickerItemBackgroundColor = in.readInt();
        indicatorTextColor = in.readInt();
        indicatorSolidColor = in.readInt();
        indicatorBorderCheckedColor = in.readInt();
        indicatorBorderUncheckedColor = in.readInt();
        isToolbarBehavior = in.readByte() != 0;
        isFabBehavior = in.readByte() != 0;
        isPickPicture = in.readByte() != 0;
        isPickVideo = in.readByte() != 0;
        isPickGif = in.readByte() != 0;
        takerConfig = in.readParcelable(TakerConfig.class.getClassLoader());
        cropperConfig = in.readParcelable(CropperConfig.class.getClassLoader());
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeTypedList(userPickedSet);
        dest.writeInt(threshold);
        dest.writeInt(spanCount);
        dest.writeInt(toolbarBkgColor);
        dest.writeInt(toolbarBkgDrawableResId);
        dest.writeInt(pickerBackgroundColor);
        dest.writeInt(pickerItemBackgroundColor);
        dest.writeInt(indicatorTextColor);
        dest.writeInt(indicatorSolidColor);
        dest.writeInt(indicatorBorderCheckedColor);
        dest.writeInt(indicatorBorderUncheckedColor);
        dest.writeByte((byte) (isToolbarBehavior ? 1 : 0));
        dest.writeByte((byte) (isFabBehavior ? 1 : 0));
        dest.writeByte((byte) (isPickPicture ? 1 : 0));
        dest.writeByte((byte) (isPickVideo ? 1 : 0));
        dest.writeByte((byte) (isPickGif ? 1 : 0));
        dest.writeParcelable(takerConfig, flags);
        dest.writeParcelable(cropperConfig, flags);
    }

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

    public static final Creator<PickerConfig> CREATOR = new Creator<PickerConfig>() {
        @Override
        public PickerConfig createFromParcel(Parcel in) {
            return new PickerConfig(in);
        }

        @Override
        public PickerConfig[] newArray(int size) {
            return new PickerConfig[size];
        }
    };

    public static Builder Builder() {
        return new Builder();
    }

    /**
     * 用户已经选中的集合
     */
    private ArrayList<MediaMeta> userPickedSet;

    /**
     * 最大选取阈值
     */
    private int threshold = 9;

    /**
     * 每行展示数量
     */
    private int spanCount = 3;

    /**
     * Toolbar 背景
     */
    private int toolbarBkgColor = COLOR_DEFAULT;
    private int toolbarBkgDrawableResId = INVALIDATE_VALUE;

    /**
     * 整体背景色
     */
    private int pickerBackgroundColor = INVALIDATE_VALUE;
    private int pickerItemBackgroundColor = Color.WHITE;

    /**
     * 指示器背景色
     */
    private int indicatorTextColor = Color.WHITE;
    private int indicatorSolidColor = COLOR_DEFAULT;
    private int indicatorBorderCheckedColor = indicatorSolidColor;
    private int indicatorBorderUncheckedColor = Color.WHITE;

    /**
     * 控制 Flag
     */
    private boolean isToolbarBehavior = false;
    private boolean isFabBehavior = false;
    private boolean isPickPicture = true;
    private boolean isPickVideo = false;
    private boolean isPickGif = false;

    /**
     * 其他功能的 Config
     */
    private TakerConfig takerConfig;
    private CropperConfig cropperConfig;

    private PickerConfig() {
    }

    @NonNull
    public ArrayList<MediaMeta> getUserPickedSet() {
        return userPickedSet;
    }

    public int getThreshold() {
        return threshold;
    }

    public int getSpanCount() {
        return spanCount;
    }

    public int getToolbarBkgColor() {
        return toolbarBkgColor;
    }

    public int getToolbarBkgDrawableResId() {
        return toolbarBkgDrawableResId;
    }

    public int getPickerBackgroundColor() {
        return pickerBackgroundColor;
    }

    public int getPickerItemBackgroundColor() {
        return pickerItemBackgroundColor;
    }

    public int getIndicatorTextColor() {
        return indicatorTextColor;
    }

    public int getIndicatorSolidColor() {
        return indicatorSolidColor;
    }

    public int getIndicatorBorderCheckedColor() {
        return indicatorBorderCheckedColor;
    }

    public int getIndicatorBorderUncheckedColor() {
        return indicatorBorderUncheckedColor;
    }

    public boolean isToolbarBehavior() {
        return isToolbarBehavior;
    }

    public boolean isFabBehavior() {
        return isFabBehavior;
    }

    public boolean isPickVideo() {
        return isPickVideo;
    }

    public boolean isPickGif() {
        return isPickGif;
    }

    public boolean isPickPicture() {
        return isPickPicture;
    }

    @Nullable
    public TakerConfig getTakerConfig() {
        return takerConfig;
    }

    @Nullable
    public CropperConfig getCropperConfig() {
        return cropperConfig;
    }

    /**
     * Used in package.
     */
    boolean isCameraSupport() {
        return takerConfig != null;
    }

    /**
     * Used in package.
     */
    boolean isCropSupport() {
        return cropperConfig != null;
    }

    public Builder rebuild() {
        return new Builder(this);
    }

    public static class Builder {

        private PickerConfig mConfig;

        private Builder() {
            mConfig = new PickerConfig();
        }

        private Builder(@NonNull PickerConfig config) {
            Preconditions.checkNotNull(config);
            this.mConfig = config;
        }

        /**
         * 设置相册可选的最大数量
         *
         * @param threshold 阈值
         */
        public Builder setThreshold(int threshold) {
            mConfig.threshold = threshold;
            return this;
        }

        /**
         * 设置用户已经选中的图片, 相册会根据 Path 比较, 在相册中打钩
         *
         * @param pickedPictures 已选中的图片
         */
        public Builder setPickedPictures(@Nullable ArrayList<MediaMeta> pickedPictures) {
            if (null != pickedPictures) {
                mConfig.userPickedSet.addAll(pickedPictures);
            }
            return this;
        }

        public Builder setSpanCount(int count) {
            mConfig.spanCount = count;
            return this;
        }

        /**
         * 设置 Toolbar 的背景色
         */
        public Builder setToolbarBackgroundColor(@ColorInt int color) {
            mConfig.toolbarBkgColor = color;
            return this;
        }

        /**
         * 设置 Toolbar 的背景图片
         *
         * @param drawableRes drawable 资源 ID
         */
        public Builder setToolbarBackgroundDrawableRes(@DrawableRes int drawableRes) {
            mConfig.toolbarBkgDrawableResId = drawableRes;
            return this;
        }

        /**
         * 设置图片选择器的背景色
         */
        public Builder setPickerBackgroundColor(@ColorInt int color) {
            mConfig.pickerBackgroundColor = color;
            return this;
        }

        /**
         * 设置图片选择器的背景色
         */
        public Builder setPickerItemBackgroundColor(@ColorInt int color) {
            mConfig.pickerItemBackgroundColor = color;
            return this;
        }

        /**
         * 设置选择索引的边框颜色
         *
         * @param textColor 边框的颜色
         */
        public Builder setIndicatorTextColor(@ColorInt int textColor) {
            mConfig.indicatorTextColor = textColor;
            return this;
        }

        /**
         * 设置选择索引的边框颜色
         *
         * @param solidColor 边框的颜色
         */
        public Builder setIndicatorSolidColor(@ColorInt int solidColor) {
            mConfig.indicatorSolidColor = solidColor;
            return this;
        }

        /**
         * 设置选择索引的边框颜色
         *
         * @param checkedColor   选中的边框颜色的 Res Id
         * @param uncheckedColor 未选中的边框颜色的Res Id
         */
        public Builder setIndicatorBorderColor(@ColorInt int checkedColor, @ColorInt int uncheckedColor) {
            mConfig.indicatorBorderCheckedColor = checkedColor;
            mConfig.indicatorBorderUncheckedColor = uncheckedColor;
            return this;
        }

        /**
         * 是否设置 Toolbar Behavior 动画
         */
        public Builder isToolbarScrollable(boolean isToolbarScrollable) {
            mConfig.isToolbarBehavior = isToolbarScrollable;
            return this;
        }

        /**
         * 是否设置 Fab Behavior 滚动动画
         */
        public Builder isFabScrollable(boolean isFabScrollable) {
            mConfig.isFabBehavior = isFabScrollable;
            return this;
        }

        /**
         * 是否支持选取视频
         *
         * @param isPickVideo if true is support.
         */
        public Builder isPickVideo(boolean isPickVideo) {
            mConfig.isPickVideo = isPickVideo;
            return this;
        }

        /**
         * 是否支持选取视频
         *
         * @param isPickPicture if true is support.
         */
        public Builder isPickPicture(boolean isPickPicture) {
            mConfig.isPickPicture = isPickPicture;
            return this;
        }


        /**
         * 是否支持选取 GIF 图
         *
         * @param isPickGif if true is support.
         */
        public Builder isPickGif(boolean isPickGif) {
            mConfig.isPickGif = isPickGif;
            return this;
        }

        /**
         * 裁剪项的配置
         *
         * @param cropperConfig if null is deny crop, if not null is granted.
         */
        public Builder setCropConfig(@Nullable CropperConfig cropperConfig) {
            mConfig.cropperConfig = cropperConfig;
            return this;
        }

        /**
         * 拍摄项的配置
         *
         * @param takerConfig if null is deny taker, if not null is granted.
         */
        public Builder setCameraConfig(@Nullable TakerConfig takerConfig) {
            mConfig.takerConfig = takerConfig;
            return this;
        }

        public PickerConfig build() {
            if (mConfig.threshold > 0 && mConfig.userPickedSet == null) {
                mConfig.userPickedSet = new ArrayList<>(mConfig.threshold);
            }
            return mConfig;
        }

    }

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerContract.java
================================================
package com.sharry.lib.album;

import android.content.Context;
import android.view.View;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

/**
 * PicturePicture MVP 的约束
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/6/13.
 */
interface PickerContract {

    interface IView {

        void setToolbarScrollable(boolean isScrollable);

        void setToolbarBackgroundColor(int color);

        void setToolbarBackgroundDrawable(@DrawableRes int drawableId);

        void setFabColor(int color);

        void setFabVisible(boolean isVisible);

        void setBackgroundColor(int color);

        void setSpanCount(int spanCount);

        void setPickerAdapter(@NonNull PickerConfig config, @NonNull ArrayList<MediaMeta> metas,
                              @NonNull ArrayList<MediaMeta> userPickedMetas);

        void setFolderAdapter(@NonNull ArrayList<FolderModel> allFolders);

        void setPictureFolderText(@NonNull String folderName);

        void setToolbarEnsureText(@NonNull CharSequence content);

        void setPreviewText(@NonNull CharSequence content);

        void notifyDisplaySetItemChanged(int changedIndex);

        void notifyDisplaySetChanged();

        void notifyFolderDataSetChanged();

        void notifyNewMetaInsertToFirst();

        void showMsg(@NonNull String msg);

        String getString(@StringRes int resId);

        void setProgressBarVisible(boolean visible);

        void setResultAndFinish(@NonNull ArrayList<MediaMeta> pickedPaths);
    }

    interface IPresenter {

        boolean handlePictureChecked(@Nullable MediaMeta checkedMeta);

        void handlePictureUnchecked(@Nullable MediaMeta removedMeta);

        void handlePickedSetChanged(MediaMeta mediaMeta);

        void handleCameraClicked();

        void handlePictureClicked(int position, @Nullable View sharedElement);

        void handleFolderChecked(int position);

        void handlePreviewClicked();

        void handleEnsureClicked();

        void handleRecycleViewDraw(RecyclerView parent);

        void handleViewDestroy();
    }

    interface IModel {

        interface Callback {

            void onFetched(@NonNull ArrayList<FolderModel> folderModels);

        }

        void fetchData(Context context, boolean pickPicture, boolean supportGif, boolean supportVideo, final Callback listener);

        void stopIfFetching();

    }

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerManager.java
================================================
package com.sharry.lib.album;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;

import androidx.annotation.NonNull;

import java.util.ArrayList;

import static android.app.Activity.RESULT_OK;

/**
 * 图片选择器的管理类
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 4/28/2019 5:03 PM
 */
public class PickerManager {

    public static final String TAG = PickerManager.class.getSimpleName();
    private static String[] sRequirePermissions = {
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
    };

    public static PickerManager with(@NonNull Context context) {
        if (context instanceof Activity) {
            Activity activity = (Activity) context;
            return new PickerManager(activity);
        } else {
            throw new IllegalArgumentException("PickerManager.with -> Context can not cast to Activity");
        }
    }

    private Activity mActivity;
    private PickerConfig mConfig;

    private PickerManager(Activity activity) {
        this.mActivity = activity;
    }

    /**
     * 设置图片加载方案
     */
    public PickerManager setLoaderEngine(@NonNull ILoaderEngine loader) {
        Preconditions.checkNotNull(loader, "Please ensure ILoaderEngine not null!");
        Loader.setLoaderEngine(loader);
        return this;
    }

    /**
     * 设置图片选择的配置
     */
    public PickerManager setPickerConfig(@NonNull PickerConfig config) {
        this.mConfig = Preconditions.checkNotNull(config, "Please ensure PickerConfig not null!");
        return this;
    }

    /**
     * 发起请求
     *
     * @param callbackLambda 图片选中的回调
     */
    public void start(@NonNull final PickerCallbackLambda callbackLambda) {
        Preconditions.checkNotNull(callbackLambda, "Please ensure PickerCallback not null!");
        start(new PickerCallback() {
            @Override
            public void onPickedComplete(@NonNull ArrayList<MediaMeta> userPickedSet) {
                callbackLambda.onPicked(userPickedSet);
            }

            @Override
            public void onPickedFailed() {
                callbackLambda.onPicked(null);
            }
        });
    }

    /**
     * 发起请求
     *
     * @param pickerCallback 图片选中的回调
     */
    public void start(@NonNull final PickerCallback pickerCallback) {
        Preconditions.checkNotNull(pickerCallback, "Please ensure PickerCallback not null!");
        Preconditions.checkNotNull(mConfig, "Please ensure U set PickerConfig correct!");
        PermissionsHelper.with(mActivity)
                .request(sRequirePermissions)
                .execute(new PermissionsCallback() {
                    @Override
                    public void onResult(boolean granted) {
                        if (granted) {
                            startActual(pickerCallback);
                        }
                    }
                });
    }

    /**
     * 处理 PickerActivity 的启动
     */
    private void startActual(@NonNull final PickerCallback pickerCallback) {
        // 1. 若开启了裁剪, 则只能选中一张图片
        if (mConfig.isCropSupport()) {
            mConfig.rebuild()
                    .setThreshold(1)
                    .setPickedPictures(null)
                    .build();
        }
        // 2. 获取回调的 Fragment
        CallbackFragment callbackFragment = CallbackFragment.getInstance(mActivity);
        if (callbackFragment == null) {
            pickerCallback.onPickedFailed();
            return;
        }
        callbackFragment.setCallback(new CallbackFragment.Callback() {
            @Override
            public void onActivityResult(int requestCode, int resultCode, Intent data) {
                ArrayList<MediaMeta> metas;
                if (resultCode == RESULT_OK && requestCode == PickerActivity.REQUEST_CODE && null != data
                        && (metas = data.getParcelableArrayListExtra(PickerActivity.RESULT_EXTRA_PICKED_PICTURES)) != null) {
                    pickerCallback.onPickedComplete(metas);
                } else {
                    pickerCallback.onPickedFailed();
                }
            }
        });
        PickerActivity.launchActivityForResult(mActivity, callbackFragment, mConfig);
    }

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerModel.java
================================================
package com.sharry.lib.album;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.sharry.lib.album.Constants.MIME_TYPE_3GP;
import static com.sharry.lib.album.Constants.MIME_TYPE_AIV;
import static com.sharry.lib.album.Constants.MIME_TYPE_FLV;
import static com.sharry.lib.album.Constants.MIME_TYPE_GIF;
import static com.sharry.lib.album.Constants.MIME_TYPE_JPEG;
import static com.sharry.lib.album.Constants.MIME_TYPE_MKV;
import static com.sharry.lib.album.Constants.MIME_TYPE_MOV;
import static com.sharry.lib.album.Constants.MIME_TYPE_MP4;
import static com.sharry.lib.album.Constants.MIME_TYPE_MPG;
import static com.sharry.lib.album.Constants.MIME_TYPE_PNG;
import static com.sharry.lib.album.Constants.MIME_TYPE_RMVB;
import static com.sharry.lib.album.Constants.MIME_TYPE_VOB;
import static com.sharry.lib.album.Constants.MIME_TYPE_WEBP;
import static com.sharry.lib.album.FileUtil.getLastFileName;
import static com.sharry.lib.album.FileUtil.getParentFolderPath;

/**
 * MVP frame model associated with PicturePicker.
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.2
 * @since 2018/8/30 20:00
 */
class PickerModel implements PickerContract.IModel {

    private static final String TAG = PickerModel.class.getSimpleName();
    private static final ThreadPoolExecutor FETCH_EXECUTOR;

    static {
        FETCH_EXECUTOR = new ThreadPoolExecutor(
                // 4 个线程并发即可满足需求
                // 使用 5 个, 是为了预防因其中一个线程意外阻塞而导致任务无法正常执行的问题
                5, 5,
                // 60 s 后自动销毁
                60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(@NonNull Runnable r) {
                        Thread thread = new Thread(r, PickerModel.class.getSimpleName());
                        thread.setDaemon(false);
                        return thread;
                    }
                }
        );
        // 允许核心线程销毁, 相册为低频组件, 无需持有核心线程, 防止占用过多系统资源
        FETCH_EXECUTOR.allowCoreThreadTimeOut(true);
    }

    private Future mFetchDataFuture;
    private Future mFetchPictureFuture;
    private Future mFetchGifFuture;
    private Future mFetchVideoFuture;

    PickerModel() {
    }

    @Override
    public void fetchData(final Context context, final boolean supportPicture, final boolean supportGif,
                          final boolean supportVideo, final Callback callback) {
        mFetchDataFuture = FETCH_EXECUTOR.submit(new Runnable() {

            @Override
            public void run() {
                // 用于存储遍历到的所有图片文件夹集合
                ArrayList<FolderModel> folderModels = new ArrayList<>();
                // 创建一个图片文件夹, 用于保存所有图片
                FolderModel folderAll = new FolderModel(
                        context.getString(R.string.lib_album_picker_all_picture)
                );
                folderModels.add(folderAll);
                /*
                   key 为图片所在文件夹的绝对路径
                   values 为 FolderModel 的对象
                 */
                ConcurrentHashMap<String, FolderModel> folders = new ConcurrentHashMap<>(16);
                // 等待执行结束
                try {
                    // 创建计数器
                    int count = 0;
                    if (supportPicture) count++;
                    if (supportGif) count++;
                    if (supportVideo) count++;
                    CountDownLatch latch = new CountDownLatch(count);
                    // 获取图片数据
                    if (supportPicture) {
                        mFetchPictureFuture = FETCH_EXECUTOR.submit(new PictureFetchRunnable(context, folders, folderAll, latch));
                    }
                    // 获取 GIF 数据
                    if (supportGif) {
                        mFetchGifFuture = FETCH_EXECUTOR.submit(new GifFetchRunnable(context, folders, folderAll, latch));
                    }
                    // 获取视频数据
                    if (supportVideo) {
                        mFetchVideoFuture = FETCH_EXECUTOR.submit(new VideoFetchRunnable(context, folders, folderAll, latch));
                    }
                    latch.await();
                } catch (InterruptedException e) {
                    // ignore.
                } finally {
                    // 注入数据
                    folderModels.addAll(folders.values());
                    // 回调完成
                    callback.onFetched(folderModels);
                }
            }

        });
    }

    @Override
    public void stopIfFetching() {
        if (mFetchPictureFuture != null) {
            mFetchPictureFuture.cancel(true);
        }
        if (mFetchGifFuture != null) {
            mFetchGifFuture.cancel(true);
        }
        if (mFetchVideoFuture != null) {
            mFetchVideoFuture.cancel(true);
        }
        if (mFetchDataFuture != null) {
            mFetchDataFuture.cancel(true);
        }
    }

    /**
     * The runnable for fetch picture resources.
     */
    private static class PictureFetchRunnable implements Runnable {

        private final Context context;
        private final ConcurrentHashMap<String, FolderModel> folders;
        private final FolderModel folderAll;
        private final CountDownLatch latch;

        PictureFetchRunnable(Context context,
                             ConcurrentHashMap<String, FolderModel> folders,
                             FolderModel folderAll,
                             CountDownLatch latch) {
            this.context = context;
            this.folderAll = folderAll;
            this.folders = folders;
            this.latch = latch;
        }

        @Override
        public void run() {
            Cursor cursor = createPictureCursor();
            try {
                while (cursor.moveToNext()) {
                    // 验证路径是否有效
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    if (TextUtils.isEmpty(path)) {
                        continue;
                    }
                    // 构建数据源
                    long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));
                    MediaMeta meta = MediaMeta.create(
                            Uri.withAppendedPath(
                                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                                    String.valueOf(id)),
                            path,
                            true
                    );
                    meta.date = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED));
                    meta.mimeType = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE));

                    // 1. 添加到 <所有> 目录下
                    folderAll.addMeta(meta);
                    // 2. 添加到文件所在目录
                    String folderPath = getParentFolderPath(path);
                    if (TextUtils.isEmpty(folderPath)) {
                        continue;
                    }
                    // 添加资源到缓存
                    FolderModel folder = folders.get(folderPath);
                    if (folder == null) {
                        String folderName = getLastFileName(folderPath);
                        if (TextUtils.isEmpty(folderName)) {
                            folderName = context.getString(R.string.lib_album_picker_root_folder);
                        }
                        folder = new FolderModel(folderName);
                        folders.put(folderPath, folder);
                    }
                    folder.addMeta(meta);
                }
                Log.i(TAG, "Fetch picture resource completed.");
            } catch (Throwable throwable) {
                // ignore.
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
                latch.countDown();
            }
        }

        /**
         * Create image cursor associated with this runnable.
         */
        private Cursor createPictureCursorWithGif() {
            Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            String[] projection = new String[]{
                    MediaStore.Video.Media._ID,
                    MediaStore.Images.Media.DATA,
                    MediaStore.Images.Media.DATE_ADDED,
                    MediaStore.Video.Media.MIME_TYPE
            };
            String selection = MediaStore.Images.Media.MIME_TYPE + "=? or " +
                    MediaStore.Images.Media.MIME_TYPE + "=? or " +
                    MediaStore.Images.Media.MIME_TYPE + "=? or " +
                    MediaStore.Images.Media.MIME_TYPE + "=?";
            String[] selectionArgs = new String[]{
                    MIME_TYPE_JPEG,
                    MIME_TYPE_PNG,
                    MIME_TYPE_WEBP,
                    MIME_TYPE_GIF
            };
            String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";
            return context.getContentResolver().query(uri, projection,
                    selection, selectionArgs, sortOrder);
        }

        /**
         * Create image cursor associated with this runnable.
         */
        private Cursor createPictureCursor() {
            Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            String[] projection = new String[]{
                    MediaStore.Video.Media._ID,
                    MediaStore.Images.Media.DATA,
                    MediaStore.Images.Media.DATE_ADDED,
                    MediaStore.Video.Media.MIME_TYPE
            };
            String selection = MediaStore.Images.Media.MIME_TYPE + "=? or " +
                    MediaStore.Images.Media.MIME_TYPE + "=? or " +
                    MediaStore.Images.Media.MIME_TYPE + "=?";
            String[] selectionArgs = new String[]{
                    MIME_TYPE_JPEG,
                    MIME_TYPE_PNG,
                    MIME_TYPE_WEBP
            };
            String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";
            return context.getContentResolver().query(uri, projection,
                    selection, selectionArgs, sortOrder);
        }

    }

    /**
     * The runnable for fetch gif resources.
     */
    private static class GifFetchRunnable implements Runnable {

        private final Context context;
        private final ConcurrentHashMap<String, FolderModel> folders;
        private final FolderModel folderAll;
        private final CountDownLatch latch;

        GifFetchRunnable(Context context,
                         ConcurrentHashMap<String, FolderModel> folders,
                         FolderModel folderAll,
                         CountDownLatch latch) {
            this.context = context;
            this.folderAll = folderAll;
            this.folders = folders;
            this.latch = latch;
        }

        @Override
        public void run() {
            Cursor cursor = createGifCursor();
            try {
                while (cursor.moveToNext()) {
                    // 验证路径是否有效
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    if (TextUtils.isEmpty(path)) {
                        continue;
                    }
                    // 构建数据源
                    long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));
                    MediaMeta meta = MediaMeta.create(
                            Uri.withAppendedPath(
                                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                                    String.valueOf(id)),
                            path,
                            true
                    );
                    meta.date = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED));
                    meta.mimeType = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE));
                    // 1. 添加到 <所有> 目录下
                    folderAll.addMeta(meta);
                    // 2. 添加到文件所在目录
                    String folderPath = getParentFolderPath(path);
                    if (TextUtils.isEmpty(folderPath)) {
                        continue;
                    }
                    // 添加资源到缓存
                    FolderModel folder = folders.get(folderPath);
                    if (folder == null) {
                        String folderName = getLastFileName(folderPath);
                        if (TextUtils.isEmpty(folderName)) {
                            folderName = context.getString(R.string.lib_album_picker_root_folder);
                        }
                        folder = new FolderModel(folderName);
                        folders.put(folderPath, folder);
                    }
                    folder.addMeta(meta);
                }
                Log.i(TAG, "Fetch picture resource completed.");
            } catch (Throwable throwable) {
                // ignore.
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
                latch.countDown();
            }
        }

        /**
         * Create image cursor associated with this runnable.
         */
        private Cursor createGifCursor() {
            Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            String[] projection = new String[]{
                    MediaStore.Video.Media._ID,
                    MediaStore.Images.Media.DATA,
                    MediaStore.Images.Media.DATE_ADDED,
                    MediaStore.Video.Media.MIME_TYPE
            };
            String selection = MediaStore.Images.Media.MIME_TYPE + "=?";
            String[] selectionArgs = new String[]{
                    MIME_TYPE_GIF
            };
            String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";
            return context.getContentResolver().query(uri, projection,
                    selection, selectionArgs, sortOrder);
        }

    }

    /**
     * The runnable for fetch video resources.
     */
    private static class VideoFetchRunnable implements Runnable {

        private final Context context;
        private final ConcurrentHashMap<String, FolderModel> folders;
        private final FolderModel folderAll;
        private final CountDownLatch latch;

        VideoFetchRunnable(Context context,
                           ConcurrentHashMap<String, FolderModel> folders,
                           FolderModel folderAll,
                           CountDownLatch latch) {
            this.context = context;
            this.folderAll = folderAll;
            this.folders = folders;
            this.latch = latch;
        }

        @Override
        public void run() {
            Cursor cursor = createVideoCursor();
            try {
                while (cursor.moveToNext()) {
                    // 验证路径是否有效
                    String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
                    if (TextUtils.isEmpty(path)) {
                        continue;
                    }
                    long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID));
                    MediaMeta meta = MediaMeta.create(
                            Uri.withAppendedPath(
                                    MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                                    String.valueOf(id)
                            ),
                            path,
                            false
                    );
                    meta.duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));
                    meta.date = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_ADDED));
                    meta.size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE));
                    meta.mimeType = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.MIME_TYPE));
                    // 获取缩略图
                    meta.thumbnailPath = fetchVideoThumbNail(id, path, meta.date);
                    // 添加到 <所有> 目录下
                    folderAll.addMeta(meta);
                    // 获取资源所在文件夹
                    String folderPath = getParentFolderPath(path);
                    if (TextUtils.isEmpty(folderPath)) {
                        continue;
                    }
                    // 添加资源到缓存
                    FolderModel folder = folders.get(folderPath);
                    if (folder == null) {
                        String folderName = getLastFileName(folderPath);
                        if (TextUtils.isEmpty(folderName)) {
                            folderName = context.getString(R.string.lib_album_picker_root_folder);
                        }
                        folder = new FolderModel(folderName);
                        folders.put(folderPath, folder);
                    }
                    folder.addMeta(meta);
                }
                Log.i(TAG, "Fetch video resource completed.");
            } catch (Throwable throwable) {
                // ignore.
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
                latch.countDown();
            }
        }

        /**
         * Create video cursor associated with this runnable.
         */
        private Cursor createVideoCursor() {
            Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            String[] projection = new String[]{
                    MediaStore.Video.Media._ID,
                    MediaStore.Video.Media.DATA,
                    MediaStore.Video.Media.DURATION,
                    MediaStore.Video.Media.DATE_ADDED,
                    MediaStore.Video.Media.SIZE,
                    MediaStore.Video.Media.MIME_TYPE
            };
            String selection = MediaStore.Images.Media.MIME_TYPE + "=? or " +
                    MediaStore.Video.Media.MIME_TYPE + "=? or " +
                    MediaStore.Video.Media.MIME_TYPE + "=? or " +
                    MediaStore.Video.Media.MIME_TYPE + "=? or " +
                    MediaStore.Video.Media.MIME_TYPE + "=? or " +
                    MediaStore.Video.Media.MIME_TYPE + "=? or " +
                    MediaStore.Video.Media.MIME_TYPE + "=? or " +
                    MediaStore.Video.Media.MIME_TYPE + "=? or " +
                    MediaStore.Video.Media.MIME_TYPE + "=?";
            String[] selectionArgs = new String[]{
                    MIME_TYPE_MP4,
                    MIME_TYPE_3GP,
                    MIME_TYPE_AIV,
                    MIME_TYPE_RMVB,
                    MIME_TYPE_VOB,
                    MIME_TYPE_FLV,
                    MIME_TYPE_MKV,
                    MIME_TYPE_MOV,
                    MIME_TYPE_MPG,
            };
            String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";
            return context.getContentResolver().query(uri, projection,
                    selection, selectionArgs, sortOrder);
        }

        /**
         * 获取视频缩略图地址
         */
        @Nullable
        private String fetchVideoThumbNail(long id, String path, long date) {
            String thumbNailPath = null;
            Cursor cursor = createThumbnailCursor(id);
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    thumbNailPath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Thumbnails.DATA));
                }
                cursor.close();
            }
            return thumbNailPath;
        }

        private Cursor createThumbnailCursor(long id) {
            Uri uri = MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI;
            String[] projection = new String[]{
                    MediaStore.Video.Thumbnails.DATA,
                    MediaStore.Video.Thumbnails.VIDEO_ID
            };
            String selection = MediaStore.Video.Thumbnails.VIDEO_ID + "=?";
            String[] selectionArgs = new String[]{String.valueOf(id)};
            return context.getContentResolver().query(uri, projection, selection,
                    selectionArgs, null);
        }
    }

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerPresenter.java
================================================
package com.sharry.lib.album;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

import java.text.MessageFormat;
import java.util.ArrayList;


/**
 * MVP frame presenter associated with PicturePicker.
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.3
 * @since 2018/9/1 10:17
 */
class PickerPresenter implements PickerContract.IPresenter,
        TakerCallbackLambda,
        CropperCallbackLambda {

    /**
     * View associated with this presenter.
     */
    private final PickerContract.IView mView;

    /**
     * Model associated with this presenter.
     */
    private final PickerContract.IModel mModel;

    /**
     * Config associated with the PicturePicker.
     */
    private final PickerConfig mPickerConfig;

    /**
     * Config associated with the PictureWatcher.
     */
    private final WatcherConfig mWatcherConfig;

    /**
     * Data Source.
     */
    private ArrayList<FolderModel> mFolderModels;
    private final ArrayList<MediaMeta> mPickedSet;

    /**
     * Current checked set.
     */
    private final ArrayList<MediaMeta> mDisplaySet = new ArrayList<>();
    private FolderModel mCheckedFolder;

    PickerPresenter(@NonNull PickerContract.IView view, @NonNull PickerConfig config) {
        this.mView = view;
        this.mPickerConfig = config;
        this.mPickedSet = mPickerConfig.getUserPickedSet();
        this.mWatcherConfig = WatcherConfig.Builder()
                .setThreshold(mPickerConfig.getThreshold())
                .setIndicatorTextColor(mPickerConfig.getIndicatorTextColor())
                .setIndicatorSolidColor(mPickerConfig.getIndicatorSolidColor())
                .setIndicatorBorderColor(
                        mPickerConfig.getIndicatorBorderCheckedColor(),
                        mPickerConfig.getIndicatorBorderUncheckedColor()
                )
                .setUserPickedSet(mPickedSet)
                .build();
        this.mModel = new PickerModel();
        setupView();
        fetchData((Context) mView);
    }

    //////////////////////////////////////////////PickerContract.IPresenter/////////////////////////////////////////////////

    @Override
    public boolean handlePictureChecked(MediaMeta checkedMeta) {
        boolean result = isCanPickedPicture(true);
        if (result && mPickedSet.add(checkedMeta)) {
            mView.setToolbarEnsureText(buildEnsureText());
            mView.setPreviewText(buildPreviewText());
        }
        return result;
    }

    @Override
    public void handlePictureUnchecked(MediaMeta removedMeta) {
        if (mPickedSet.remove(removedMeta)) {
            mView.setToolbarEnsureText(buildEnsureText());
            mView.setPreviewText(buildPreviewText());
        }
    }

    @Override
    public void handlePickedSetChanged(MediaMeta mediaMeta) {
        if (mediaMeta == null) {
            return;
        }
        int changedPos = mDisplaySet.indexOf(mediaMeta);
        if (changedPos != -1) {
            mView.setToolbarEnsureText(buildEnsureText());
            mView.setPreviewText(buildPreviewText());
            mView.notifyDisplaySetItemChanged(mPickerConfig.isCameraSupport() ?
                    changedPos + 1 : changedPos);
        }
    }

    @Override
    public void handleCameraClicked() {
        if (mPickerConfig.getTakerConfig() != null) {
            TakerManager.with((Context) mView)
                    .setConfig(
                            mPickerConfig.getTakerConfig().rebuild()
                                    // 取消相机拍摄后的裁剪动作, 由 Picker ensure 时触发
                                    .setVideoRecord(mPickerConfig.isPickVideo())
                                    .build()
                    )
                    .take(this);
        }
    }

    @Override
    public void handlePictureClicked(int position, View sharedElement) {
        WatcherManager.with((Context) mView)
                .setSharedElement(sharedElement)
                .setLoaderEngine(Loader.getPictureLoader())
                .setConfig(
                        mWatcherConfig.rebuild()
                                .setDisplayDataSet(mDisplaySet, position)
                                .build()
                )
                .start();
    }

    @Override
    public void handlePreviewClicked() {
        if (!isCanPreview()) {
            return;
        }
        WatcherManager.with((Context) mView)
                .setLoaderEngine(Loader.getPictureLoader())
                .setConfig(
                        mWatcherConfig.rebuild()
                                .setDisplayDataSet(mPickedSet, 0)
                                .build()
                )
                .start();
    }

    @Override
    public void handleFolderChecked(int position) {
        performFolderChecked(position);
    }

    @Override
    public void handleEnsureClicked() {
        if (!isCanEnsure()) {
            return;
        }
        // 不需要裁剪, 直接返回
        if (mPickerConfig.isCropSupport() && mPickedSet.get(0).isPicture()) {
            // 启动裁剪
            assert mPickerConfig.getCropperConfig() != null;
            CropperManager.with((Context) mView)
                    .setConfig(
                            mPickerConfig.getCropperConfig().rebuild()
                                    .setOriginUri(mPickedSet.get(0).contentUri)
                                    .build()
                    )
                    .crop(this);
        } else {
            mView.setResultAndFinish(mPickedSet);
        }
    }

    @Override
    public void handleRecycleViewDraw(RecyclerView parent) {
        // Cache view bounds.
        SharedElementHelper.CACHES.clear();
        for (int i = 0; i < parent.getChildCount(); i++) {
            View child = parent.getChildAt(i);
            int adapterPosition = parent.getChildAdapterPosition(child) +
                    (mPickerConfig.isCameraSupport() ? -1 : 0);
            SharedElementHelper.CACHES.put(adapterPosition, SharedElementHelper.Bounds.parseFrom(
                    child, adapterPosition));
        }
    }

    @Override
    public void handleViewDestroy() {
        // 终止 mModel 获取数据
        mModel.stopIfFetching();
        // 清空共享元素缓存的数据
        SharedElementHelper.CACHES.clear();
    }

    //////////////////////////////////////////////TakerCallback/////////////////////////////////////////////////

    @Override
    public void onCameraTake(@Nullable MediaMeta newMeta) {
        if (newMeta == null) {
            return;
        }
        // 1. 添加到 <当前展示> 的文件夹下
        mCheckedFolder.addMeta(newMeta);
        // 2. 添加到 <所有文件> 的文件夹下
        FolderModel folderAll = mFolderModels.get(0);
        if (folderAll != mCheckedFolder) {
            folderAll.addMeta(newMeta);
        }
        // 3. 更新展示的集合
        mDisplaySet.add(0, newMeta);
        // 3.1 判断是否可以继续选择
        if (isCanPickedPicture(false)) {
            mPickedSet.add(newMeta);
            mView.setToolbarEnsureText(buildEnsureText());
            mView.setPreviewText(buildPreviewText());
        }
        // 3.2 通知 UI 更新视图
        mView.notifyNewMetaInsertToFirst();
        mView.notifyFolderDataSetChanged();
    }

    ////////////////////////////////////////////// CropperCallbackLambda /////////////////////////////////////////////////

    @Override
    public void onCropped(@Nullable MediaMeta mediaMeta) {
        if (mediaMeta == null) {
            return;
        }
        mPickedSet.clear();
        mPickedSet.add(mediaMeta);
        mView.setResultAndFinish(mPickedSet);
    }

    private void setupView() {
        // 配置 UI 视图
        mView.setToolbarScrollable(mPickerConfig.isToolbarBehavior());
        mView.setFabVisible(mPickerConfig.isFabBehavior());
        if (mPickerConfig.getToolbarBkgColor() != PickerConfig.INVALIDATE_VALUE) {
            mView.setToolbarBackgroundColor(mPickerConfig.getToolbarBkgColor());
            mView.setFabColor(mPickerConfig.getToolbarBkgColor());
        }
        if (mPickerConfig.getToolbarBkgDrawableResId() != PickerConfig.INVALIDATE_VALUE) {
            mView.setToolbarBackgroundDrawable(mPickerConfig.getToolbarBkgDrawableResId());
        }
        if (mPickerConfig.getPickerBackgroundColor() != PickerConfig.INVALIDATE_VALUE) {
            mView.setBackgroundColor(mPickerConfig.getPickerBackgroundColor());
        }
        // 设置图片的列数
        mView.setSpanCount(mPickerConfig.getSpanCount());
        // 设置 RecyclerView 的 Adapter
        mView.setPickerAdapter(mPickerConfig, mDisplaySet, mPickedSet);
    }

    private void fetchData(Context context) {
        mView.setProgressBarVisible(true);
        mModel.fetchData(
                context.getApplicationContext(),
                mPickerConfig.isPickPicture(),
                mPickerConfig.isPickGif(),
                mPickerConfig.isPickVideo(),
                new PickerContract.IModel.Callback() {

                    private final Handler mainHandler = new Handler(Looper.getMainLooper());

                    @Override
                    public void onFetched(@NonNull final ArrayList<FolderModel> folderModels) {
                        mFolderModels = folderModels;
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mView.setProgressBarVisible(false);
                                mView.setFolderAdapter(mFolderModels);
                                performFolderChecked(0);
                            }
                        });
                    }

                }
        );
    }

    /**
     * 执行展示文件夹的操作
     */
    private void performFolderChecked(int position) {
        // Upgrade checked folder.
        mCheckedFolder = mFolderModels.get(position);
        mDisplaySet.clear();
        mDisplaySet.addAll(mCheckedFolder.getMetas());
        // Notify view displays paths changed.
        mView.notifyDisplaySetChanged();
        // Set folder text associated with view.
        mView.setPictureFolderText(mCheckedFolder.getName());
        // Set ensure text associated with view toolbar.
        mView.setToolbarEnsureText(buildEnsureText());
        // Set preview text associated with view.
        mView.setPreviewText(buildPreviewText());
    }

    /**
     * 是否可以继续选择图片
     *
     * @param isShowFailedMsg 是否提示失败原因
     * @return true is can picked, false is cannot picked.
     */
    private boolean isCanPickedPicture(boolean isShowFailedMsg) {
        if (mPickedSet.size() == mPickerConfig.getThreshold()) {
            if (isShowFailedMsg) {
                mView.showMsg(mView.getString(R.string.lib_album_picker_tips_over_threshold_prefix)
                        + mPickerConfig.getThreshold()
                        + mView.getString(R.string.lib_album_picker_tips_over_threshold_suffix)
                );
            }
            return false;
        }
        return true;
    }

    /**
     * 是否可以启动图片预览
     *
     * @return true is can launch, false is cannot launch.
     */
    private boolean isCanPreview() {
        if (mPickedSet.isEmpty()) {
            mView.showMsg(mView.getString(R.string.lib_album_picker_tips_preview_failed));
            return false;
        }
        return true;
    }

    /**
     * 是否可以发起确认请求
     *
     * @return true is can ensure, false is cannot ensure.
     */
    private boolean isCanEnsure() {
        if (mPickedSet.isEmpty()) {
            mView.showMsg(mView.getString(R.string.lib_album_picker_tips_ensure_failed));
            return false;
        }
        return true;
    }

    /**
     * 构建标题确认文本
     */
    private CharSequence buildEnsureText() {
        return MessageFormat.format(
                "{0} ({1}/{2})",
                mView.getString(R.string.lib_album_picker_ensure),
                mPickedSet.size(),
                mPickerConfig.getThreshold()
        );
    }

    /**
     * 构建预览文本
     */
    private CharSequence buildPreviewText() {
        return MessageFormat.format(
                "{0} ({1})",
                mView.getString(R.string.lib_album_picker_preview),
                mPickedSet.size()
        );
    }

}


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_bottom_indicator.xml
================================================
<vector android:height="24dp" android:tint="#FFFFFF"
    android:viewportHeight="24.0" android:viewportWidth="24.0"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="#FF000000" android:pathData="M3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM7,13h14v-2L7,11v2zM7,17h14v-2L7,15v2zM7,7v2h14L21,7L7,7z"/>
</vector>


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_camera_header.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="50dp"
    android:height="50dp"
    android:viewportHeight="1024"
    android:viewportWidth="1024">
    <path
        android:fillColor="#888888"
        android:pathData="M908.98,224.73L743.75,224.73v-14.5c0,-64.14 -52.2,-116.34 -116.35,-116.34L396.57,93.89c-64.14,0 -116.32,52.19 -116.32,116.34v14.5L114.98,224.73C51.58,224.73 0,276.25 0,339.57v475.7c0,63.31 51.58,114.83 114.98,114.83h794c63.43,0 115.02,-51.52 115.02,-114.83L1024,339.57c0,-63.32 -51.58,-114.84 -115.02,-114.84zM963.32,815.27c0,29.86 -24.38,54.16 -54.34,54.16h-794c-29.95,0 -54.3,-24.3 -54.3,-54.16L60.68,339.57c0,-29.86 24.35,-54.16 54.3,-54.16h195.61a30.32,30.32 0,0 0,30.34 -30.34v-44.84a55.7,55.7 0,0 1,55.64 -55.66h230.83c30.7,0 55.67,24.96 55.67,55.66v44.84a30.32,30.32 0,0 0,30.34 30.34h195.57c29.95,0 54.34,24.3 54.34,54.16v475.7z" />
    <path
        android:fillColor="#888888"
        android:pathData="M512,350.62c-106.84,0 -193.74,86.9 -193.74,193.7 0,106.82 86.9,193.71 193.74,193.71 106.83,0 193.74,-86.9 193.74,-193.71 0,-106.81 -86.9,-193.7 -193.74,-193.7zM512,677.35c-73.36,0 -133.06,-59.67 -133.06,-133.03 0,-73.35 59.7,-133.02 133.06,-133.02s133.06,59.67 133.06,133.02c0,73.36 -59.7,133.03 -133.06,133.03z" />
</vector>


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_fab.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:tint="#FFFFFF"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_gif.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
  <path
      android:pathData="M944,299H692c-4.4,0 -8,3.6 -8,8v406c0,4.4 3.6,8 8,8h59.2c4.4,0 8,-3.6 8,-8V549.9h168.2c4.4,0 8,-3.6 8,-8V495c0,-4.4 -3.6,-8 -8,-8H759.2V364.2H944c4.4,0 8,-3.6 8,-8V307c0,-4.4 -3.6,-8 -8,-8zM588,300h-56c-4.4,0 -8,3.6 -8,8v406c0,4.4 3.6,8 8,8h56c4.4,0 8,-3.6 8,-8V308c0,-4.4 -3.6,-8 -8,-8zM452,500.9H290.5c-4.4,0 -8,3.6 -8,8v43.7c0,4.4 3.6,8 8,8h94.9l-0.3,8.9c-1.2,58.8 -45.6,98.5 -110.9,98.5 -76.2,0 -123.9,-59.7 -123.9,-156.7 0,-95.8 46.8,-155.2 121.5,-155.2 54.8,0 93.1,26.9 108.5,75.4h76.2c-13.6,-87.2 -86,-143.4 -184.7,-143.4C150,288 72,375.2 72,511.9 72,650.2 149.1,736 273,736c114.1,0 187,-70.7 187,-181.6v-45.5c0,-4.4 -3.6,-8 -8,-8z"
      android:fillColor="#ffffff"/>
</vector>


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_right_arrow.xml
================================================
<vector android:height="24dp" android:tint="#FFFFFF"
    android:viewportHeight="24.0" android:viewportWidth="24.0"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="#ffffffff" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_video_default.xml
================================================
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="#ff000000" />

</shape>

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_video_play.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="40dp"
    android:height="40dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="#FFFFFFFF"
        android:pathData="M8,5v14l11,-7z" />
</vector>


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_activity_picker.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--标题栏-->
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.sharry.lib.album.toolbar.SToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:backIcon="@drawable/ic_album_picker_right_arrow"
            app:statusBarStyle="Transparent"
            app:subItemInterval="10dp"
            app:titleGravity="Left"
            app:titleTextSize="18dp" />

    </com.google.android.material.appbar.AppBarLayout>

    <!--中心容器-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_picker"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingBottom="60dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:visibility="visible" />

    <!--底部的照片文件夹选择器-->
    <LinearLayout
        android:id="@+id/ll_bottom_menu"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:orientation="vertical"
        app:behavior_peekHeight="60dp"
        app:layout_behavior="@string/bottom_sheet_behavior">

        <RelativeLayout
            android:id="@+id/rv_menu_nav_container"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:background="@color/lib_album_picker_bottom_menu_nav_bg_collapsed_color">

            <ImageView
                android:id="@+id/iv_nav_indicator"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:paddingLeft="20dp"
                app:srcCompat="@drawable/ic_album_picker_bottom_indicator" />

            <TextView
                android:id="@+id/tv_folder_name"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_toRightOf="@+id/iv_nav_indicator"
                android:drawablePadding="5dp"
                android:gravity="center_vertical"
                android:paddingStart="5dp"
                android:paddingLeft="5dp"
                android:text="@string/lib_album_picker_all_picture"
                android:textColor="@color/lib_album_picker_bottom_menu_nav_text_collapsed_color"
                android:textSize="15dp" />

            <TextView
                android:id="@+id/tv_preview"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_alignParentRight="true"
                android:layout_gravity="center_vertical|right"
                android:gravity="center"
                android:paddingLeft="20dp"
                android:paddingRight="20dp"
                android:text="@string/lib_album_picker_preview"
                android:textColor="@color/lib_album_picker_bottom_menu_nav_text_collapsed_color"
                android:textSize="14dp" />

        </RelativeLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycle_folders"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/lib_album_picker_bottom_menu_content_folders_bg_color" />

    </LinearLayout>

    <!--悬浮按钮-->
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="20dp"
        android:layout_marginBottom="70dp"
        app:layout_anchor="@+id/rv_picker"
        app:layout_anchorGravity="bottom|right"
        app:layout_behavior="@string/lib_album_picker_fab_behavior"
        app:srcCompat="@drawable/ic_album_picker_fab" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_recycle_item_folder.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:foreground="?attr/selectableItemBackgroundBorderless"
    android:orientation="horizontal"
    android:paddingLeft="20dp"
    android:paddingTop="5dp"
    android:paddingRight="20dp"
    android:paddingBottom="5dp">

    <ImageView
        android:id="@+id/iv_preview"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/tv_folder_name"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="15dp"
        android:gravity="center" />

</LinearLayout>

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_recycle_item_header_camera.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/iv_picture"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:foreground="?attr/selectableItemBackgroundBorderless"
    android:paddingLeft="30dp"
    android:paddingRight="30dp"
    android:scaleType="fitCenter"
    app:srcCompat="@drawable/ic_album_picker_camera_header" />

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_recycle_item_picture.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="1dp">

    <ImageView
        android:id="@+id/iv_picture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:foreground="?attr/selectableItemBackgroundBorderless" />

    <com.sharry.lib.album.CheckedIndicatorView
        android:id="@+id/check_indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|top"
        android:textColor="@android:color/white" />

    <ImageView
        android:id="@+id/iv_gif_tag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left|bottom"
        android:layout_margin="5dp"
        app:srcCompat="@drawable/ic_album_picker_gif" />

</FrameLayout>

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_recycle_item_video.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tool="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="1dp">

    <ImageView
        android:id="@+id/iv_picture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/ic_album_picker_video_default" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/lib_album_picker_recycle_item_video_bg_color"
        android:foreground="?attr/selectableItemBackgroundBorderless"
        android:scaleType="centerInside"
        app:srcCompat="@drawable/ic_album_picker_video_play" />

    <com.sharry.lib.album.CheckedIndicatorView
        android:id="@+id/check_indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|top"
        android:textColor="@android:color/white" />

    <TextView
        android:id="@+id/tv_duration"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:padding="10dp"
        android:textColor="@android:color/white"
        android:textSize="13dp"
        tool:text="15:00" />

</FrameLayout>

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/values/picker_colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <color name="lib_album_picker_theme_primary_color">#ff00b0ff</color>
    <color name="lib_album_picker_theme_primary_dark_color">#ff00b0ff</color>
    <color name="lib_album_picker_theme_accent_color">#ff64b6f6</color>

    <color name="lib_album_picker_bottom_menu_nav_bg_collapsed_color">#a9000000</color>
    <color name="lib_album_picker_bottom_menu_navi_bg_expand_color">#ffffffff</color>
    <color name="lib_album_picker_bottom_menu_nav_text_collapsed_color">#ffffffff</color>
    <color name="lib_album_picker_bottom_menu_navi_text_expand_color">#ff333333</color>
    <color name="lib_album_picker_bottom_menu_content_folders_bg_color">#ffffffff</color>
    <color name="lib_album_picker_recycle_item_video_bg_color">#A9313131</color>

</resources>

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/values/picker_strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="lib_album_picker_ensure">Ensure</string>
    <string name="lib_album_picker_preview">Preview</string>
    <string name="lib_album_picker_all_picture">All</string>
    <string name="lib_album_picker_root_folder">Storage Card</string>
    <string name="lib_album_picker_tips_over_threshold_prefix">Pick a maximum of&#160;</string>
    <string name="lib_album_picker_tips_over_threshold_suffix">&#160;pictures.</string>
    <string name="lib_album_picker_tips_fetch_album_failed">Fetch album data failed.</string>
    <string name="lib_album_picker_tips_ensure_failed">Please pick at least one picture.</string>
    <string name="lib_album_picker_tips_preview_failed">@string/lib_album_picker_tips_ensure_failed</string>
    <string name="lib_album_picker_fab_behavior" translatable="false">com.sharry.lib.album.PicturePickerFabBehavior</string>

</resources>

================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/values/picker_themes.xml
================================================
<resources>

    <style name="PickerTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/lib_album_picker_theme_primary_color</item>
        <item name="colorPrimaryDark">@color/lib_album_picker_theme_primary_dark_color</item>
        <item name="colorAccent">@color/lib_album_picker_theme_accent_color</item>
    </style>

</resources>


================================================
FILE: lib-album/src/main/picker/com/sharry/lib/album/res/values-zh/picker_strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!--PickerActivity 需要用到的字符串-->
    <string name="lib_album_picker_ensure">确认</string>
    <string name="lib_album_picker_preview">预览</string>
    <string name="lib_album_picker_all_picture">所有</string>
    <string name="lib_album_picker_root_folder">SD 卡根目录</string>
    <string name="lib_album_picker_tips_over_threshold_prefix">最多只可选择&#160;</string>
    <string name="lib_album_picker_tips_over_threshold_suffix">&#160;张图片</string>
    <string name="lib_album_picker_tips_fetch_album_failed">获取相册数据失败</string>
    <string name="lib_album_picker_tips_ensure_failed">至少选择一张图片</string>
    <string name="lib_album_picker_tips_preview_failed">@string/lib_album_picker_tips_ensure_failed</string>

</resources>

================================================
FILE: lib-album/src/main/player/com/sharry/lib/album/VideoPlayerActivity.java
================================================
package com.sharry.lib.album;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.VideoView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatSeekBar;
import androidx.constraintlayout.widget.ConstraintLayout;

/**
 * 视频播放的 Activity
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2019-09-04 16:33
 */
public class VideoPlayerActivity extends AppCompatActivity implements MediaPlayer.OnPreparedListener,
        MediaPlayer.OnCompletionListener,
        MediaPlayer.OnErrorListener,
        View.OnClickListener {

    private static final String EXTRA_MEDIA_META = "extra_media_meta";

    public static void launch(Context context, MediaMeta mediaMeta) {
        Intent intent = new Intent(context, VideoPlayerActivity.class);
        intent.putExtra(EXTRA_MEDIA_META, mediaMeta);
        context.startActivity(intent);
        if (context instanceof Activity) {
            ((Activity) context).overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
        }
    }

    private static final String TAG = VideoPlayerActivity.class.getSimpleName();
    private static final int MSG_WHAT_UPDATE_PROGRESS = 0;
    private static final int MAXIMUM_TRY_AGAIN_THRESHOLD = 3;

    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what != MSG_WHAT_UPDATE_PROGRESS) {
                return;
            }
            // 更新进度
            updateProgress();
        }
    };

    /**
     * 播放数据源
     */
    private MediaMeta mDataSource;

    /**
     * Widgets.
     */
    private VideoView mVideoView;
    private TextView mTvCurrent;
    private AppCompatSeekBar mSeekBar;
    private TextView mTvTotal;
    private ConstraintLayout mClControl;
    private ImageView mIvControl;
    private ObjectAnimator mPanelHindAnimator;
    private ObjectAnimator mPanelShowAnimator;

    /**
     * 条件控制变量
     */
    private boolean mIsPrepared = false;
    private boolean mIsPaused = false;
    private int mCountTryAgain = 0;
    private int mCurrentDuration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.lib_album_activity_video_player);
        parseIntent();
        initViews();
        prepare();
    }

    @Override
    protected void onResume() {
        super.onResume();
        showControlPanel();
    }

    @Override
    protected void onPause() {
        super.onPause();
        pause();
    }

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

    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }

    ////////////////////////////////////MediaPlayer.OnCompletionListener///////////////////////////////////////

    /**
     * Callback when SetVideoPath / onResume
     */
    @Override
    public void onPrepared(MediaPlayer mp) {
        // 标记为准备完成
        mIsPrepared = true;
        // 清空重试次数
        mCountTryAgain = 0;
        // 为 View 注入数据
        mTvTotal.setText(DateUtil.format(mp.getDuration()));
        mSeekBar.setMax(mp.getDuration());
        // 若之前是暂停, 则显示控制面板
        if (mIsPaused) {
            showControlPanel();
        }
        // 若之前非暂停, 则直接播放
        else {
            play();
        }
    }

    ////////////////////////////////////MediaPlayer.OnErrorListener///////////////////////////////////////

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        if (mCountTryAgain++ < MAXIMUM_TRY_AGAIN_THRESHOLD) {
            Log.w(TAG, "Occurred an error, try again " + mCountTryAgain + " time");
            prepare();
            return true;
        } else {
            // 重置视图
            reset();
            // 标记为准备失败
            mIsPrepared = false;
            return false;
        }
    }

    ////////////////////////////////////MediaPlayer.OnCompletionListener///////////////////////////////////////

    @Override
    public void onCompletion(MediaPlayer mp) {
        reset();
        mIsPrepared = false;
    }

    ////////////////////////////////////View.OnClickListener///////////////////////////////////////

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.video_view || v.getId() == R.id.fl_container) {
            showControlPanel();
        } else if (v.getId() == R.id.cl_control) {
            hindControlPanel();
        } else if (v.getId() == R.id.iv_control) {
            if (mIsPrepared) {
                if (mVideoView.isPlaying()) {
                    pause();
                } else {
                    play();
                }
            } else {
                prepare();
            }
        } else {
            // ignore.
        }
    }

    //////////////////////////////////// Private methods ///////////////////////////////////////

    private void parseIntent() {
        mDataSource = getIntent().getParcelableExtra(EXTRA_MEDIA_META);
    }

    private void initViews() {
        // 设置外层窗体, 让其可响应事件
        findViewById(R.id.fl_container).setOnClickListener(this);
        // 配置 Video View
        mVideoView = findViewById(R.id.video_view);
        mVideoView.setOnPreparedListener(this);
        mVideoView.setOnCompletionListener(this);
        mVideoView.setOnErrorListener(this);
        mVideoView.setOnClickListener(this);
        // 配置 SeekBar
        mSeekBar = findViewById(R.id.seek_bar);
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                mCurrentDuration = progress;
                mTvCurrent.setText(DateUtil.format(mCurrentDuration));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                mVideoView.seekTo(mCurrentDuration);
            }
        });
        // 控制中心
        mClControl = findViewById(R.id.cl_control);
        mClControl.setOnClickListener(this);
        // 控制按钮
        mIvControl = findViewById(R.id.iv_control);
        mIvControl.setOnClickListener(this);
        // 播放进度
        mTvCurrent = findViewById(R.id.tv_current);
        mTvTotal = findViewById(R.id.tv_total);
    }

    private void updateProgress() {
        // 更新进度
        mTvCurrent.setText(DateUtil.format(mVideoView.getCurrentPosition()));
        mSeekBar.setProgress(mVideoView.getCurrentPosition());
        // 进行下一次更新
        if (mVideoView != null && mVideoView.isPlaying()) {
            mHandler.sendEmptyMessageDelayed(MSG_WHAT_UPDATE_PROGRESS, 1000);
        }
    }

    private void showControlPanel() {
        if (mPanelShowAnimator == null) {
            mPanelShowAnimator = ObjectAnimator.ofFloat(mClControl,
                    "alpha", 0f, 1f);
            mPanelShowAnimator.setDuration(200);
            mPanelShowAnimator.addListener(new AnimatorListenerAdapter() {

                @Override
                public void onAnimationStart(Animator animation) {
                    mClControl.setVisibility(View.VISIBLE);
                }

            });
        }
        if (mPanelShowAnimator.isRunning()) {
            return;
        }
        mPanelShowAnimator.start();
    }

    private void hindControlPanel() {
        if (mPanelHindAnimator == null) {
            mPanelHindAnimator = ObjectAnimator.ofFloat(mClControl,
                    "alpha", 1f, 0f);
            mPanelHindAnimator.setDuration(200);
            mPanelHindAnimator.addListener(new AnimatorListenerAdapter() {

                @Override
                public void onAnimationStart(Animator animation) {
                    mClControl.setVisibility(View.VISIBLE);
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mClControl.setVisibility(View.INVISIBLE);
                }
            }
Download .txt
gitextract_4ugyik6s/

├── .gitignore
├── README.md
├── SharryKey
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   ├── release/
│   │   └── output.json
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── sharry/
│           │           └── app/
│           │               └── salbum/
│           │                   ├── MainActivity.kt
│           │                   └── WatermarkPreviewerRenderer.java
│           └── res/
│               ├── drawable/
│               │   ├── app_activity_main_launcher.xml
│               │   └── ic_launcher_foreground.xml
│               ├── layout/
│               │   └── app_activity_main.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   └── ic_launcher_round.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── ic_launcher_background.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               ├── values-zh/
│               │   └── strings.xml
│               └── xml/
│                   └── provider_paths.xml
├── assert/
│   └── SAlbum-1.0.1.apk
├── build.gradle
├── git
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── lib-album/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── base/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── ILoaderEngine.java
│           │                   ├── Loader.java
│           │                   └── MediaMeta.java
│           ├── copper/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── CropperCallback.java
│           │                   ├── CropperCallbackLambda.java
│           │                   ├── CropperConfig.java
│           │                   ├── CropperFragment.java
│           │                   └── CropperManager.java
│           ├── picker/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── FolderAdapter.java
│           │                   ├── FolderModel.java
│           │                   ├── PickerActivity.java
│           │                   ├── PickerAdapter.java
│           │                   ├── PickerCallback.java
│           │                   ├── PickerCallbackLambda.java
│           │                   ├── PickerConfig.java
│           │                   ├── PickerContract.java
│           │                   ├── PickerManager.java
│           │                   ├── PickerModel.java
│           │                   ├── PickerPresenter.java
│           │                   └── res/
│           │                       ├── drawable/
│           │                       │   ├── ic_album_picker_bottom_indicator.xml
│           │                       │   ├── ic_album_picker_camera_header.xml
│           │                       │   ├── ic_album_picker_fab.xml
│           │                       │   ├── ic_album_picker_gif.xml
│           │                       │   ├── ic_album_picker_right_arrow.xml
│           │                       │   ├── ic_album_picker_video_default.xml
│           │                       │   └── ic_album_picker_video_play.xml
│           │                       ├── layout/
│           │                       │   ├── lib_album_activity_picker.xml
│           │                       │   ├── lib_album_recycle_item_folder.xml
│           │                       │   ├── lib_album_recycle_item_header_camera.xml
│           │                       │   ├── lib_album_recycle_item_picture.xml
│           │                       │   └── lib_album_recycle_item_video.xml
│           │                       ├── values/
│           │                       │   ├── picker_colors.xml
│           │                       │   ├── picker_strings.xml
│           │                       │   └── picker_themes.xml
│           │                       └── values-zh/
│           │                           └── picker_strings.xml
│           ├── player/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── VideoPlayerActivity.java
│           │                   └── res/
│           │                       ├── drawable/
│           │                       │   ├── ic_album_player_video_pasue.xml
│           │                       │   └── ic_album_player_video_play.xml
│           │                       ├── layout/
│           │                       │   └── lib_album_activity_video_player.xml
│           │                       ├── layout-land/
│           │                       │   └── lib_album_activity_video_player.xml
│           │                       └── values/
│           │                           └── player_color.xml
│           ├── taker/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── AspectRatioFragment.java
│           │                   ├── ITakerContract.java
│           │                   ├── TakerActivity.java
│           │                   ├── TakerCallback.java
│           │                   ├── TakerCallbackLambda.java
│           │                   ├── TakerConfig.java
│           │                   ├── TakerManager.java
│           │                   ├── TakerPresenter.java
│           │                   └── res/
│           │                       ├── drawable/
│           │                       │   ├── ic_album_taker_aspect.xml
│           │                       │   ├── ic_album_taker_camera_switch.xml
│           │                       │   ├── ic_album_taker_denied.xml
│           │                       │   ├── ic_album_taker_full_screen.xml
│           │                       │   └── ic_album_taker_granted.xml
│           │                       ├── layout/
│           │                       │   └── lib_ablum_activity_taker.xml
│           │                       ├── values/
│           │                       │   ├── taker_colors.xml
│           │                       │   └── taker_strings.xml
│           │                       └── values-zh/
│           │                           └── taker_strings.xml
│           ├── utils/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── ActivityStateUtil.java
│           │                   ├── CallbackFragment.java
│           │                   ├── ColorUtil.java
│           │                   ├── CompressUtil.java
│           │                   ├── Constants.java
│           │                   ├── DateUtil.java
│           │                   ├── DensityUtil.java
│           │                   ├── FileUtil.java
│           │                   ├── PermissionsCallback.java
│           │                   ├── PermissionsFragment.java
│           │                   ├── PermissionsHelper.java
│           │                   ├── Preconditions.java
│           │                   ├── SharedElementHelper.java
│           │                   └── VersionUtil.java
│           ├── watcher/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── album/
│           │                   ├── DisplayAdapter.java
│           │                   ├── PickedPanelAdapter.java
│           │                   ├── WatcherActivity.java
│           │                   ├── WatcherCallback.java
│           │                   ├── WatcherCallbackLambda.java
│           │                   ├── WatcherConfig.java
│           │                   ├── WatcherContract.java
│           │                   ├── WatcherFragment.java
│           │                   ├── WatcherManager.java
│           │                   ├── WatcherPresenter.java
│           │                   └── res/
│           │                       ├── drawable/
│           │                       │   ├── ic_album_watcher_right_arrow.xml
│           │                       │   └── ic_album_watcher_video_play.xml
│           │                       ├── layout/
│           │                       │   ├── lib_album_activity_watcher.xml
│           │                       │   └── lib_album_fragment_watcher_pager.xml
│           │                       ├── values/
│           │                       │   ├── watcher_colors.xml
│           │                       │   ├── watcher_strings.xml
│           │                       │   └── watcher_themes.xml
│           │                       └── values-zh/
│           │                           └── watcher_strings.xml
│           └── widget/
│               └── com/
│                   └── sharry/
│                       └── lib/
│                           └── album/
│                               ├── CheckedIndicatorView.java
│                               ├── DraggableViewPager.java
│                               ├── PicturePickerFabBehavior.java
│                               ├── RecorderButton.java
│                               ├── photoview/
│                               │   ├── Compat.java
│                               │   ├── CustomGestureDetector.java
│                               │   ├── OnGestureListener.java
│                               │   ├── OnMatrixChangedListener.java
│                               │   ├── OnOutsidePhotoTapListener.java
│                               │   ├── OnPhotoTapListener.java
│                               │   ├── OnScaleChangedListener.java
│                               │   ├── OnSingleFlingListener.java
│                               │   ├── OnViewDragListener.java
│                               │   ├── OnViewTapListener.java
│                               │   ├── PhotoView.java
│                               │   ├── PhotoViewAttacher.java
│                               │   └── Util.java
│                               └── toolbar/
│                                   ├── AppBarHelper.java
│                                   ├── Builder.java
│                                   ├── ImageViewOptions.java
│                                   ├── Options.java
│                                   ├── SToolbar.java
│                                   ├── Style.java
│                                   ├── TextViewOptions.java
│                                   ├── Utils.java
│                                   ├── ViewOptions.java
│                                   └── res/
│                                       └── values/
│                                           └── lib_toolbar_attrs.xml
├── lib-media-recorder/
│   ├── .gitignore
│   ├── CMakeLists.txt
│   ├── Readme.markdown
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── api/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── IMediaRecorder.java
│           │                       ├── IRecorderCallback.java
│           │                       ├── Options.java
│           │                       └── SMediaRecorder.java
│           ├── cpp/
│           │   ├── ConstDefine.h
│           │   ├── JNICall.cpp
│           │   ├── JNICall.h
│           │   ├── OpenSLRecorder.cpp
│           │   ├── OpenSLRecorder.h
│           │   ├── RecordBuffer.cpp
│           │   ├── RecordBuffer.h
│           │   └── native-bridge-recorder.cpp
│           ├── encoder/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── AACEncoder.java
│           │                       ├── EncodeType.java
│           │                       ├── EncoderFactory.java
│           │                       ├── H264Encoder.java
│           │                       ├── H264Render.java
│           │                       ├── IAudioEncoder.java
│           │                       └── IVideoEncoder.java
│           ├── muxer/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── IMuxer.java
│           │                       ├── MPEG4Muxer.java
│           │                       ├── MuxerFactory.java
│           │                       └── MuxerType.java
│           ├── pcmprovider/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── DefaultPCMProvider.java
│           │                       ├── IPCMProvider.java
│           │                       └── OpenSLESPCMProvider.java
│           ├── recorder/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── media/
│           │                   └── recorder/
│           │                       ├── AudioRecorder.java
│           │                       ├── BaseMediaRecorder.java
│           │                       └── VideoRecorder.java
│           └── utils/
│               └── com/
│                   └── sharry/
│                       └── lib/
│                           └── media/
│                               └── recorder/
│                                   ├── AVPoolExecutor.java
│                                   ├── FileUtil.java
│                                   ├── NetworkUtil.java
│                                   └── VersionUtil.java
├── lib-opengles/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── opengles/
│           │                   ├── surface/
│           │                   │   └── ContextSharedGLSurfaceView.java
│           │                   ├── texture/
│           │                   │   ├── GLTextureView.java
│           │                   │   └── ITextureRenderer.java
│           │                   └── util/
│           │                       ├── EglCore.java
│           │                       ├── FboHelper.java
│           │                       ├── GlMatrixUtil.java
│           │                       └── GlUtil.java
│           └── utils/
│               └── com/
│                   └── sharry/
│                       └── lib/
│                           └── opengles/
│                               ├── EglCore.java
│                               └── GlUtil.java
├── lib-scamera/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── api/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   └── SCameraView.java
│           ├── common/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   ├── AspectRatio.java
│           │                   ├── CameraContext.java
│           │                   ├── Constants.java
│           │                   ├── Size.java
│           │                   └── SizeMap.java
│           ├── device/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   ├── AbsCameraDevice.java
│           │                   ├── Camera1Device.java
│           │                   └── ICameraDevice.java
│           ├── orientation/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   └── ScreenOrientationDetector.java
│           ├── previewer/
│           │   └── com/
│           │       └── sharry/
│           │           └── lib/
│           │               └── camera/
│           │                   ├── DefaultPreviewerRenderer.java
│           │                   ├── IPreviewer.java
│           │                   ├── Previewer.java
│           │                   ├── PreviewerRendererImpl.java
│           │                   ├── PreviewerRendererWrapper.java
│           │                   └── ScaleType.java
│           └── res/
│               ├── raw/
│               │   ├── camera_fragment_shader.glsl
│               │   └── camera_vertex_shader.glsl
│               └── values/
│                   ├── attrs.xml
│                   ├── public.xml
│                   └── styles.xml
└── settings.gradle
Download .txt
SYMBOL INDEX (1665 symbols across 135 files)

FILE: app/src/main/java/com/sharry/app/salbum/WatermarkPreviewerRenderer.java
  class WatermarkPreviewerRenderer (line 24) | public class WatermarkPreviewerRenderer extends PreviewerRendererWrapper {
    method WatermarkPreviewerRenderer (line 104) | public WatermarkPreviewerRenderer(Context context) {
    method onAttach (line 110) | @Override
    method setupShaders (line 122) | private void setupShaders() {
    method setupCoordinates (line 129) | private void setupCoordinates() {
    method setupWatermarkTexture (line 181) | private void setupWatermarkTexture() {
    method onSizeChanged (line 200) | @Override
    method updateWatermarkCoors (line 212) | private void updateWatermarkCoors(int surfaceWidth, int surfaceHeight) {
    method onDrawTexture (line 248) | @Override
    method drawOriginTexture (line 261) | private void drawOriginTexture(int textureId) {
    method drawWatermark (line 283) | private void drawWatermark() {
    method drawToEGLSurface (line 312) | private void drawToEGLSurface() {
    method getPreviewerTextureId (line 335) | @Override
    method onDetach (line 340) | @Override

FILE: lib-album/src/main/base/com/sharry/lib/album/ILoaderEngine.java
  type ILoaderEngine (line 15) | public interface ILoaderEngine {
    method loadPicture (line 20) | void loadPicture(@NonNull Context context, @NonNull MediaMeta mediaMet...
    method loadGif (line 25) | void loadGif(@NonNull Context context, @NonNull MediaMeta mediaMeta, @...
    method loadVideoThumbnails (line 30) | void loadVideoThumbnails(@NonNull Context context, @NonNull MediaMeta ...

FILE: lib-album/src/main/base/com/sharry/lib/album/Loader.java
  class Loader (line 17) | final class Loader {
    method setLoaderEngine (line 22) | static void setLoaderEngine(@Nullable ILoaderEngine engine) {
    method getPictureLoader (line 28) | static ILoaderEngine getPictureLoader() {
    method loadPicture (line 32) | static void loadPicture(@NonNull Context context, @NonNull MediaMeta m...
    method loadGif (line 40) | static void loadGif(@NonNull Context context, @NonNull MediaMeta media...
    method loadVideo (line 48) | static void loadVideo(@NonNull Context context, @NonNull MediaMeta med...

FILE: lib-album/src/main/base/com/sharry/lib/album/MediaMeta.java
  class MediaMeta (line 17) | public class MediaMeta implements Parcelable {
    method createPicture (line 22) | public static MediaMeta createPicture(@NonNull Uri uri) {
    method createVideo (line 29) | public static MediaMeta createVideo(@NonNull Uri uri) {
    method create (line 33) | static MediaMeta create(@NonNull Uri uri, String filePath, boolean isP...
    method createFromParcel (line 38) | @Override
    method newArray (line 43) | @Override
    method MediaMeta (line 49) | protected MediaMeta(Parcel in) {
    method writeToParcel (line 60) | @Override
    method describeContents (line 72) | @Override
    method MediaMeta (line 124) | private MediaMeta(@NonNull Uri uri, @NonNull String filePath, boolean ...
    method equals (line 130) | @Override
    method hashCode (line 142) | @Override
    method toString (line 147) | @Override
    method getContentUri (line 161) | @NonNull
    method getPath (line 166) | @NonNull
    method isPicture (line 172) | public boolean isPicture() {
    method getSize (line 176) | public long getSize() {
    method getDate (line 180) | public long getDate() {
    method getDuration (line 184) | public long getDuration() {
    method getThumbnailPath (line 188) | @Nullable
    method getMimeType (line 193) | public String getMimeType() {

FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperCallback.java
  type CropperCallback (line 12) | public interface CropperCallback {
    method onCropComplete (line 17) | void onCropComplete(@NonNull MediaMeta meta);
    method onCropFailed (line 19) | void onCropFailed();

FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperCallbackLambda.java
  type CropperCallbackLambda (line 12) | public interface CropperCallbackLambda {
    method onCropped (line 14) | void onCropped(@Nullable MediaMeta meta);

FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperConfig.java
  class CropperConfig (line 18) | public class CropperConfig implements Parcelable {
    method CropperConfig (line 20) | protected CropperConfig(Parcel in) {
    method writeToParcel (line 32) | @Override
    method describeContents (line 45) | @Override
    method createFromParcel (line 51) | @Override
    method newArray (line 56) | @Override
    method Builder (line 62) | public static Builder Builder() {
      method Builder (line 126) | private Builder() {
      method Builder (line 130) | private Builder(@NonNull CropperConfig config) {
      method setCropCircle (line 137) | public Builder setCropCircle(boolean isCropCircle) {
      method setCropSize (line 145) | public Builder setCropSize(int width, int height) {
      method setAspectSize (line 154) | public Builder setAspectSize(int x, int y) {
      method setAuthority (line 163) | public Builder setAuthority(@NonNull String authorities) {
      method setOriginUri (line 172) | public Builder setOriginUri(@NonNull Uri originUri) {
      method setRelativePath (line 190) | public Builder setRelativePath(@Nullable String relativePath) {
      method setCropQuality (line 198) | public Builder setCropQuality(int quality) {
      method build (line 203) | @NonNull
    method CropperConfig (line 76) | private CropperConfig() {
    method getOriginUri (line 79) | public Uri getOriginUri() {
    method getRelativePath (line 83) | public String getRelativePath() {
    method isCropCircle (line 87) | public boolean isCropCircle() {
    method getAuthority (line 91) | public String getAuthority() {
    method getAspectX (line 95) | public int getAspectX() {
    method getAspectY (line 99) | public int getAspectY() {
    method getOutputX (line 103) | public int getOutputX() {
    method getOutputY (line 107) | public int getOutputY() {
    method getDestQuality (line 111) | public int getDestQuality() {
    method rebuild (line 115) | public Builder rebuild() {
    class Builder (line 122) | public static class Builder {
      method Builder (line 126) | private Builder() {
      method Builder (line 130) | private Builder(@NonNull CropperConfig config) {
      method setCropCircle (line 137) | public Builder setCropCircle(boolean isCropCircle) {
      method setCropSize (line 145) | public Builder setCropSize(int width, int height) {
      method setAspectSize (line 154) | public Builder setAspectSize(int x, int y) {
      method setAuthority (line 163) | public Builder setAuthority(@NonNull String authorities) {
      method setOriginUri (line 172) | public Builder setOriginUri(@NonNull Uri originUri) {
      method setRelativePath (line 190) | public Builder setRelativePath(@Nullable String relativePath) {
      method setCropQuality (line 198) | public Builder setCropQuality(int quality) {
      method build (line 203) | @NonNull

FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperFragment.java
  class CropperFragment (line 30) | public class CropperFragment extends Fragment {
    method getInstance (line 39) | @Nullable
    method findFragmentFromActivity (line 59) | private static CropperFragment findFragmentFromActivity(@NonNull Activ...
    method onAttach (line 68) | @Override
    method onAttach (line 74) | @Override
    method onCreate (line 80) | @Override
    method cropPicture (line 89) | public void cropPicture(CropperConfig config, CropperCallback callback) {
    method onActivityResult (line 107) | @Override
    method completion (line 144) | private void completion(Intent intent, CropperConfig config, Uri origi...

FILE: lib-album/src/main/copper/com/sharry/lib/album/CropperManager.java
  class CropperManager (line 18) | public class CropperManager {
    method with (line 26) | public static CropperManager with(@NonNull Context context) {
    method CropperManager (line 38) | private CropperManager(Activity activity) {
    method setConfig (line 45) | public CropperManager setConfig(@NonNull CropperConfig config) {
    method crop (line 53) | public void crop(@NonNull final CropperCallbackLambda callback) {
    method crop (line 70) | public void crop(@NonNull final CropperCallback callback) {
    method cropActual (line 88) | private void cropActual(@NonNull final CropperCallback callback) {

FILE: lib-album/src/main/picker/com/sharry/lib/album/FolderAdapter.java
  class FolderAdapter (line 22) | class FolderAdapter extends RecyclerView.Adapter<FolderAdapter.ViewHolde...
    method FolderAdapter (line 28) | FolderAdapter(Context context, List<FolderModel> data) {
    method onCreateViewHolder (line 39) | @NonNull
    method onBindViewHolder (line 47) | @Override
    method getItemCount (line 62) | @Override
    type AdapterInteraction (line 70) | public interface AdapterInteraction {
      method onFolderChecked (line 72) | void onFolderChecked(int position);
    class ViewHolder (line 76) | class ViewHolder extends RecyclerView.ViewHolder {
      method ViewHolder (line 80) | private ViewHolder(View itemView) {

FILE: lib-album/src/main/picker/com/sharry/lib/album/FolderModel.java
  class FolderModel (line 15) | class FolderModel {
    method FolderModel (line 20) | FolderModel(String name) {
    method getName (line 24) | String getName() {
    method getMetas (line 28) | List<MediaMeta> getMetas() {
    method addMeta (line 32) | synchronized void addMeta(@NonNull MediaMeta meta) {

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerActivity.java
  class PickerActivity (line 44) | public class PickerActivity extends AppCompatActivity implements PickerC...
    method launchActivityForResult (line 65) | public static void launchActivityForResult(Activity from, Fragment res...
    method onCreate (line 97) | @Override
    method initTitle (line 107) | protected void initTitle() {
    method initViews (line 124) | protected void initViews() {
    method initPresenter (line 154) | protected void initPresenter() {
    method onReceive (line 162) | @Override
    method onReceive (line 172) | @Override
    method registerLocalBroadcast (line 178) | private void registerLocalBroadcast() {
    method onBackPressed (line 191) | @Override
    method onDestroy (line 200) | @Override
    method setToolbarBackgroundColor (line 210) | @Override
    method setToolbarBackgroundDrawable (line 215) | @Override
    method setToolbarScrollable (line 220) | @Override
    method setBackgroundColor (line 231) | @Override
    method setSpanCount (line 236) | @Override
    method setPickerAdapter (line 241) | @Override
    method setFolderAdapter (line 249) | @Override
    method setFabColor (line 254) | @Override
    method setProgressBarVisible (line 259) | @Override
    method setFabVisible (line 264) | @Override
    method setPictureFolderText (line 273) | @Override
    method setToolbarEnsureText (line 280) | @Override
    method setPreviewText (line 285) | @Override
    method notifyDisplaySetItemChanged (line 290) | @Override
    method notifyDisplaySetChanged (line 298) | @Override
    method notifyNewMetaInsertToFirst (line 306) | @Override
    method notifyFolderDataSetChanged (line 314) | @Override
    method showMsg (line 322) | @Override
    method setResultAndFinish (line 327) | @Override
    method onCameraClicked (line 337) | @Override
    method onPictureClicked (line 342) | @Override
    method onPictureChecked (line 347) | @Override
    method onPictureRemoved (line 352) | @Override
    method onFolderChecked (line 359) | @Override
    method onClick (line 367) | @Override
    class BottomMenuNavigationCallback (line 387) | private class BottomMenuNavigationCallback extends BottomSheetBehavior...
      method BottomMenuNavigationCallback (line 395) | BottomMenuNavigationCallback() {
      method onStateChanged (line 407) | @Override
      method onSlide (line 412) | @Override

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerAdapter.java
  class PickerAdapter (line 27) | class PickerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    method run (line 40) | @Override
    method PickerAdapter (line 46) | PickerAdapter(Context context,
    method getItemViewType (line 62) | @Override
    method onCreateViewHolder (line 78) | @NonNull
    method onBindViewHolder (line 97) | @Override
    method getItemCount (line 126) | @Override
    method bindPictureItem (line 134) | private void bindPictureItem(final PictureViewHolder holder, final Med...
    method bindVideoItem (line 150) | private void bindVideoItem(final VideoViewHolder holder, final MediaMe...
    method notifyCheckedIndicatorChanged (line 168) | private void notifyCheckedIndicatorChanged() {
    class CameraHeaderHolder (line 175) | class CameraHeaderHolder extends RecyclerView.ViewHolder implements Vi...
      method CameraHeaderHolder (line 177) | CameraHeaderHolder(ViewGroup parent) {
      method onClick (line 193) | @Override
    class PictureViewHolder (line 202) | class PictureViewHolder extends RecyclerView.ViewHolder implements Vie...
      method run (line 208) | @Override
      method PictureViewHolder (line 214) | PictureViewHolder(ViewGroup parent) {
      method onClick (line 236) | @Override
      method adjustItemView (line 246) | private void adjustItemView(ViewGroup parent) {
      method performPictureClicked (line 268) | private void performPictureClicked() {
      method performCheckIndicatorClicked (line 276) | private void performCheckIndicatorClicked() {
    class VideoViewHolder (line 305) | class VideoViewHolder extends RecyclerView.ViewHolder implements View....
      method run (line 311) | @Override
      method VideoViewHolder (line 317) | VideoViewHolder(ViewGroup parent) {
      method onClick (line 343) | @Override
      method adjustItemView (line 353) | private void adjustItemView(ViewGroup parent) {
      method performPictureClicked (line 375) | private void performPictureClicked() {
      method performCheckIndicatorClicked (line 383) | private void performCheckIndicatorClicked() {
    type Interaction (line 412) | interface Interaction {
      method onCameraClicked (line 414) | void onCameraClicked();
      method onPictureClicked (line 416) | void onPictureClicked(@NonNull View itemView, @NonNull Uri uri, int ...
      method onPictureChecked (line 418) | boolean onPictureChecked(@NonNull MediaMeta checkedMeta);
      method onPictureRemoved (line 420) | void onPictureRemoved(@NonNull MediaMeta removedMeta);

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerCallback.java
  type PickerCallback (line 14) | public interface PickerCallback {
    method onPickedComplete (line 21) | void onPickedComplete(@NonNull ArrayList<MediaMeta> userPickedSet);
    method onPickedFailed (line 26) | void onPickedFailed();

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerCallbackLambda.java
  type PickerCallbackLambda (line 14) | public interface PickerCallbackLambda {
    method onPicked (line 21) | void onPicked(@Nullable ArrayList<MediaMeta> userPickedSet);

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerConfig.java
  class PickerConfig (line 21) | public class PickerConfig implements Parcelable {
    method PickerConfig (line 26) | protected PickerConfig(Parcel in) {
    method writeToParcel (line 47) | @Override
    method describeContents (line 69) | @Override
    method createFromParcel (line 75) | @Override
    method newArray (line 80) | @Override
    method Builder (line 86) | public static Builder Builder() {
      method Builder (line 240) | private Builder() {
      method Builder (line 244) | private Builder(@NonNull PickerConfig config) {
      method setThreshold (line 254) | public Builder setThreshold(int threshold) {
      method setPickedPictures (line 264) | public Builder setPickedPictures(@Nullable ArrayList<MediaMeta> pick...
      method setSpanCount (line 271) | public Builder setSpanCount(int count) {
      method setToolbarBackgroundColor (line 279) | public Builder setToolbarBackgroundColor(@ColorInt int color) {
      method setToolbarBackgroundDrawableRes (line 289) | public Builder setToolbarBackgroundDrawableRes(@DrawableRes int draw...
      method setPickerBackgroundColor (line 297) | public Builder setPickerBackgroundColor(@ColorInt int color) {
      method setPickerItemBackgroundColor (line 305) | public Builder setPickerItemBackgroundColor(@ColorInt int color) {
      method setIndicatorTextColor (line 315) | public Builder setIndicatorTextColor(@ColorInt int textColor) {
      method setIndicatorSolidColor (line 325) | public Builder setIndicatorSolidColor(@ColorInt int solidColor) {
      method setIndicatorBorderColor (line 336) | public Builder setIndicatorBorderColor(@ColorInt int checkedColor, @...
      method isToolbarScrollable (line 345) | public Builder isToolbarScrollable(boolean isToolbarScrollable) {
      method isFabScrollable (line 353) | public Builder isFabScrollable(boolean isFabScrollable) {
      method isPickVideo (line 363) | public Builder isPickVideo(boolean isPickVideo) {
      method isPickPicture (line 373) | public Builder isPickPicture(boolean isPickPicture) {
      method isPickGif (line 384) | public Builder isPickGif(boolean isPickGif) {
      method setCropConfig (line 394) | public Builder setCropConfig(@Nullable CropperConfig cropperConfig) {
      method setCameraConfig (line 404) | public Builder setCameraConfig(@Nullable TakerConfig takerConfig) {
      method build (line 409) | public PickerConfig build() {
    method PickerConfig (line 140) | private PickerConfig() {
    method getUserPickedSet (line 143) | @NonNull
    method getThreshold (line 148) | public int getThreshold() {
    method getSpanCount (line 152) | public int getSpanCount() {
    method getToolbarBkgColor (line 156) | public int getToolbarBkgColor() {
    method getToolbarBkgDrawableResId (line 160) | public int getToolbarBkgDrawableResId() {
    method getPickerBackgroundColor (line 164) | public int getPickerBackgroundColor() {
    method getPickerItemBackgroundColor (line 168) | public int getPickerItemBackgroundColor() {
    method getIndicatorTextColor (line 172) | public int getIndicatorTextColor() {
    method getIndicatorSolidColor (line 176) | public int getIndicatorSolidColor() {
    method getIndicatorBorderCheckedColor (line 180) | public int getIndicatorBorderCheckedColor() {
    method getIndicatorBorderUncheckedColor (line 184) | public int getIndicatorBorderUncheckedColor() {
    method isToolbarBehavior (line 188) | public boolean isToolbarBehavior() {
    method isFabBehavior (line 192) | public boolean isFabBehavior() {
    method isPickVideo (line 196) | public boolean isPickVideo() {
    method isPickGif (line 200) | public boolean isPickGif() {
    method isPickPicture (line 204) | public boolean isPickPicture() {
    method getTakerConfig (line 208) | @Nullable
    method getCropperConfig (line 213) | @Nullable
    method isCameraSupport (line 221) | boolean isCameraSupport() {
    method isCropSupport (line 228) | boolean isCropSupport() {
    method rebuild (line 232) | public Builder rebuild() {
    class Builder (line 236) | public static class Builder {
      method Builder (line 240) | private Builder() {
      method Builder (line 244) | private Builder(@NonNull PickerConfig config) {
      method setThreshold (line 254) | public Builder setThreshold(int threshold) {
      method setPickedPictures (line 264) | public Builder setPickedPictures(@Nullable ArrayList<MediaMeta> pick...
      method setSpanCount (line 271) | public Builder setSpanCount(int count) {
      method setToolbarBackgroundColor (line 279) | public Builder setToolbarBackgroundColor(@ColorInt int color) {
      method setToolbarBackgroundDrawableRes (line 289) | public Builder setToolbarBackgroundDrawableRes(@DrawableRes int draw...
      method setPickerBackgroundColor (line 297) | public Builder setPickerBackgroundColor(@ColorInt int color) {
      method setPickerItemBackgroundColor (line 305) | public Builder setPickerItemBackgroundColor(@ColorInt int color) {
      method setIndicatorTextColor (line 315) | public Builder setIndicatorTextColor(@ColorInt int textColor) {
      method setIndicatorSolidColor (line 325) | public Builder setIndicatorSolidColor(@ColorInt int solidColor) {
      method setIndicatorBorderColor (line 336) | public Builder setIndicatorBorderColor(@ColorInt int checkedColor, @...
      method isToolbarScrollable (line 345) | public Builder isToolbarScrollable(boolean isToolbarScrollable) {
      method isFabScrollable (line 353) | public Builder isFabScrollable(boolean isFabScrollable) {
      method isPickVideo (line 363) | public Builder isPickVideo(boolean isPickVideo) {
      method isPickPicture (line 373) | public Builder isPickPicture(boolean isPickPicture) {
      method isPickGif (line 384) | public Builder isPickGif(boolean isPickGif) {
      method setCropConfig (line 394) | public Builder setCropConfig(@Nullable CropperConfig cropperConfig) {
      method setCameraConfig (line 404) | public Builder setCameraConfig(@Nullable TakerConfig takerConfig) {
      method build (line 409) | public PickerConfig build() {

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerContract.java
  type PickerContract (line 21) | interface PickerContract {
    type IView (line 23) | interface IView {
      method setToolbarScrollable (line 25) | void setToolbarScrollable(boolean isScrollable);
      method setToolbarBackgroundColor (line 27) | void setToolbarBackgroundColor(int color);
      method setToolbarBackgroundDrawable (line 29) | void setToolbarBackgroundDrawable(@DrawableRes int drawableId);
      method setFabColor (line 31) | void setFabColor(int color);
      method setFabVisible (line 33) | void setFabVisible(boolean isVisible);
      method setBackgroundColor (line 35) | void setBackgroundColor(int color);
      method setSpanCount (line 37) | void setSpanCount(int spanCount);
      method setPickerAdapter (line 39) | void setPickerAdapter(@NonNull PickerConfig config, @NonNull ArrayLi...
      method setFolderAdapter (line 42) | void setFolderAdapter(@NonNull ArrayList<FolderModel> allFolders);
      method setPictureFolderText (line 44) | void setPictureFolderText(@NonNull String folderName);
      method setToolbarEnsureText (line 46) | void setToolbarEnsureText(@NonNull CharSequence content);
      method setPreviewText (line 48) | void setPreviewText(@NonNull CharSequence content);
      method notifyDisplaySetItemChanged (line 50) | void notifyDisplaySetItemChanged(int changedIndex);
      method notifyDisplaySetChanged (line 52) | void notifyDisplaySetChanged();
      method notifyFolderDataSetChanged (line 54) | void notifyFolderDataSetChanged();
      method notifyNewMetaInsertToFirst (line 56) | void notifyNewMetaInsertToFirst();
      method showMsg (line 58) | void showMsg(@NonNull String msg);
      method getString (line 60) | String getString(@StringRes int resId);
      method setProgressBarVisible (line 62) | void setProgressBarVisible(boolean visible);
      method setResultAndFinish (line 64) | void setResultAndFinish(@NonNull ArrayList<MediaMeta> pickedPaths);
    type IPresenter (line 67) | interface IPresenter {
      method handlePictureChecked (line 69) | boolean handlePictureChecked(@Nullable MediaMeta checkedMeta);
      method handlePictureUnchecked (line 71) | void handlePictureUnchecked(@Nullable MediaMeta removedMeta);
      method handlePickedSetChanged (line 73) | void handlePickedSetChanged(MediaMeta mediaMeta);
      method handleCameraClicked (line 75) | void handleCameraClicked();
      method handlePictureClicked (line 77) | void handlePictureClicked(int position, @Nullable View sharedElement);
      method handleFolderChecked (line 79) | void handleFolderChecked(int position);
      method handlePreviewClicked (line 81) | void handlePreviewClicked();
      method handleEnsureClicked (line 83) | void handleEnsureClicked();
      method handleRecycleViewDraw (line 85) | void handleRecycleViewDraw(RecyclerView parent);
      method handleViewDestroy (line 87) | void handleViewDestroy();
    type IModel (line 90) | interface IModel {
      type Callback (line 92) | interface Callback {
        method onFetched (line 94) | void onFetched(@NonNull ArrayList<FolderModel> folderModels);
      method fetchData (line 98) | void fetchData(Context context, boolean pickPicture, boolean support...
      method stopIfFetching (line 100) | void stopIfFetching();

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerManager.java
  class PickerManager (line 21) | public class PickerManager {
    method with (line 29) | public static PickerManager with(@NonNull Context context) {
    method PickerManager (line 41) | private PickerManager(Activity activity) {
    method setLoaderEngine (line 48) | public PickerManager setLoaderEngine(@NonNull ILoaderEngine loader) {
    method setPickerConfig (line 57) | public PickerManager setPickerConfig(@NonNull PickerConfig config) {
    method start (line 67) | public void start(@NonNull final PickerCallbackLambda callbackLambda) {
    method start (line 87) | public void start(@NonNull final PickerCallback pickerCallback) {
    method startActual (line 105) | private void startActual(@NonNull final PickerCallback pickerCallback) {

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerModel.java
  class PickerModel (line 45) | class PickerModel implements PickerContract.IModel {
    method newThread (line 59) | @Override
    method PickerModel (line 76) | PickerModel() {
    method fetchData (line 79) | @Override
    method stopIfFetching (line 132) | @Override
    class PictureFetchRunnable (line 151) | private static class PictureFetchRunnable implements Runnable {
      method PictureFetchRunnable (line 158) | PictureFetchRunnable(Context context,
      method run (line 168) | @Override
      method createPictureCursorWithGif (line 223) | private Cursor createPictureCursorWithGif() {
      method createPictureCursor (line 249) | private Cursor createPictureCursor() {
    class GifFetchRunnable (line 275) | private static class GifFetchRunnable implements Runnable {
      method GifFetchRunnable (line 282) | GifFetchRunnable(Context context,
      method run (line 292) | @Override
      method createGifCursor (line 346) | private Cursor createGifCursor() {
    class VideoFetchRunnable (line 368) | private static class VideoFetchRunnable implements Runnable {
      method VideoFetchRunnable (line 375) | VideoFetchRunnable(Context context,
      method run (line 385) | @Override
      method createVideoCursor (line 443) | private Cursor createVideoCursor() {
      method fetchVideoThumbNail (line 481) | @Nullable
      method createThumbnailCursor (line 494) | private Cursor createThumbnailCursor(long id) {

FILE: lib-album/src/main/picker/com/sharry/lib/album/PickerPresenter.java
  class PickerPresenter (line 23) | class PickerPresenter implements PickerContract.IPresenter,
    method PickerPresenter (line 59) | PickerPresenter(@NonNull PickerContract.IView view, @NonNull PickerCon...
    method handlePictureChecked (line 80) | @Override
    method handlePictureUnchecked (line 90) | @Override
    method handlePickedSetChanged (line 98) | @Override
    method handleCameraClicked (line 112) | @Override
    method handlePictureClicked (line 126) | @Override
    method handlePreviewClicked (line 139) | @Override
    method handleFolderChecked (line 154) | @Override
    method handleEnsureClicked (line 159) | @Override
    method handleRecycleViewDraw (line 180) | @Override
    method handleViewDestroy (line 193) | @Override
    method onCameraTake (line 203) | @Override
    method onCropped (line 230) | @Override
    method setupView (line 240) | private void setupView() {
    method fetchData (line 260) | private void fetchData(Context context) {
    method performFolderChecked (line 291) | private void performFolderChecked(int position) {
    method isCanPickedPicture (line 312) | private boolean isCanPickedPicture(boolean isShowFailedMsg) {
    method isCanPreview (line 330) | private boolean isCanPreview() {
    method isCanEnsure (line 343) | private boolean isCanEnsure() {
    method buildEnsureText (line 354) | private CharSequence buildEnsureText() {
    method buildPreviewText (line 366) | private CharSequence buildPreviewText() {

FILE: lib-album/src/main/player/com/sharry/lib/album/VideoPlayerActivity.java
  class VideoPlayerActivity (line 32) | public class VideoPlayerActivity extends AppCompatActivity implements Me...
    method launch (line 39) | public static void launch(Context context, MediaMeta mediaMeta) {
    method handleMessage (line 53) | @Override
    method onCreate (line 88) | @Override
    method onResume (line 97) | @Override
    method onPause (line 103) | @Override
    method onBackPressed (line 109) | @Override
    method finish (line 115) | @Override
    method onPrepared (line 126) | @Override
    method onError (line 147) | @Override
    method onCompletion (line 164) | @Override
    method onClick (line 172) | @Override
    method parseIntent (line 195) | private void parseIntent() {
    method initViews (line 199) | private void initViews() {
    method updateProgress (line 238) | private void updateProgress() {
    method showControlPanel (line 248) | private void showControlPanel() {
    method hindControlPanel (line 268) | private void hindControlPanel() {
    method prepare (line 294) | private void prepare() {
    method play (line 299) | private void play() {
    method pause (line 307) | private void pause() {
    method stop (line 314) | private void stop() {
    method reset (line 318) | private void reset() {

FILE: lib-album/src/main/taker/com/sharry/lib/album/AspectRatioFragment.java
  class AspectRatioFragment (line 39) | public class AspectRatioFragment extends DialogFragment {
    method newInstance (line 46) | static AspectRatioFragment newInstance(AspectRatio[] ratios,
    method onAttach (line 56) | @Override
    method onDetach (line 62) | @Override
    method onCreateDialog (line 68) | @NonNull
    class AspectRatioAdapter (line 88) | private static class AspectRatioAdapter extends BaseAdapter {
      method AspectRatioAdapter (line 93) | AspectRatioAdapter(AspectRatio[] ratios, AspectRatio current) {
      method getCount (line 98) | @Override
      method getItem (line 103) | @Override
      method getItemId (line 108) | @Override
      method getView (line 113) | @Override
      class ViewHolder (line 134) | private static class ViewHolder {
    type Listener (line 140) | public interface Listener {
      method onAspectRatioSelected (line 141) | void onAspectRatioSelected(@NonNull AspectRatio ratio);

FILE: lib-album/src/main/taker/com/sharry/lib/album/ITakerContract.java
  type ITakerContract (line 18) | public interface ITakerContract {
    type IView (line 20) | interface IView {
      method setPreviewAspect (line 22) | void setPreviewAspect(@NonNull AspectRatio aspect);
      method setPreviewFullScreen (line 24) | void setPreviewFullScreen(boolean fullScreen);
      method setPreviewRenderer (line 26) | void setPreviewRenderer(@NonNull String rendererClassName);
      method setRecordButtonVisible (line 28) | void setRecordButtonVisible(boolean visible);
      method setSupportVideoRecord (line 30) | void setSupportVideoRecord(boolean isVideoRecord);
      method setMaxRecordDuration (line 32) | void setMaxRecordDuration(long maxDuration);
      method setRecordButtonProgress (line 34) | void setRecordButtonProgress(long currentDuration);
      method setProgressColor (line 36) | void setProgressColor(int recordProgressColor);
      method setPreviewSource (line 38) | void setPreviewSource(@NonNull Bitmap bitmap);
      method startVideoPlayer (line 40) | void startVideoPlayer(@NonNull Uri uri);
      method stopVideoPlayer (line 42) | void stopVideoPlayer();
      method getCameraBitmap (line 49) | Bitmap getCameraBitmap();
      method setStatus (line 60) | void setStatus(@Status int status);
      method getStatus (line 62) | @Status
      method toast (line 65) | void toast(@StringRes int resId);
      method setResult (line 67) | void setResult(@NonNull MediaMeta mediaMeta);
    type IPresenter (line 71) | interface IPresenter {
      method handleGranted (line 73) | void handleGranted();
      method handleDenied (line 75) | void handleDenied();
      method handleTakePicture (line 77) | void handleTakePicture();
      method handleRecordStart (line 79) | void handleRecordStart(SCameraView cameraView);
      method handleRecordFinish (line 81) | void handleRecordFinish(long duration);
      method handleVideoPlayFailed (line 83) | void handleVideoPlayFailed();
      method handleViewDestroy (line 85) | void handleViewDestroy();

FILE: lib-album/src/main/taker/com/sharry/lib/album/TakerActivity.java
  class TakerActivity (line 36) | public class TakerActivity extends AppCompatActivity implements
    method launchForResult (line 46) | public static void launchForResult(CallbackFragment fragment, TakerCon...
    method onCreate (line 78) | @Override
    method onResume (line 87) | @Override
    method onPause (line 102) | @Override
    method onDestroy (line 109) | @Override
    method setPreviewAspect (line 118) | @Override
    method setPreviewFullScreen (line 123) | @Override
    method setPreviewRenderer (line 132) | @Override
    method setRecordButtonVisible (line 146) | @Override
    method setSupportVideoRecord (line 151) | @Override
    method setMaxRecordDuration (line 156) | @Override
    method setRecordButtonProgress (line 161) | @Override
    method setProgressColor (line 166) | @Override
    method setPreviewSource (line 171) | @Override
    method startVideoPlayer (line 176) | @Override
    method stopVideoPlayer (line 181) | @Override
    method getCameraBitmap (line 186) | @Override
    method setStatus (line 191) | @Override
    method getStatus (line 237) | @Override
    method toast (line 242) | @Override
    method setResult (line 247) | @Override
    method onAspectRatioSelected (line 258) | @Override
    method onTakePicture (line 265) | @Override
    method onRecordStart (line 270) | @Override
    method onRecordFinish (line 277) | @Override
    method initTitle (line 286) | private void initTitle() {
    method initViews (line 336) | private void initViews() {
    method initPresenter (line 386) | private void initPresenter() {

FILE: lib-album/src/main/taker/com/sharry/lib/album/TakerCallback.java
  type TakerCallback (line 12) | public interface TakerCallback {
    method onCameraTakeComplete (line 19) | void onCameraTakeComplete(@NonNull MediaMeta newMeta);
    method onTakeFailed (line 24) | void onTakeFailed();

FILE: lib-album/src/main/taker/com/sharry/lib/album/TakerCallbackLambda.java
  type TakerCallbackLambda (line 12) | public interface TakerCallbackLambda {
    method onCameraTake (line 19) | void onCameraTake(@Nullable MediaMeta newMeta);

FILE: lib-album/src/main/taker/com/sharry/lib/album/TakerConfig.java
  class TakerConfig (line 24) | public class TakerConfig implements Parcelable {
    method TakerConfig (line 26) | protected TakerConfig(Parcel in) {
    method writeToParcel (line 42) | @Override
    method describeContents (line 59) | @Override
    method createFromParcel (line 65) | @Override
    method newArray (line 70) | @Override
    method Builder (line 79) | @NonNull
      method Builder (line 241) | private Builder() {
      method Builder (line 245) | private Builder(@NonNull TakerConfig config) {
      method setRelativePath (line 262) | public Builder setRelativePath(@Nullable String relativePath) {
      method setAuthority (line 270) | public Builder setAuthority(@NonNull String authority) {
      method setPictureQuality (line 279) | public Builder setPictureQuality(int quality) {
      method setPreviewAspect (line 287) | public Builder setPreviewAspect(@Aspect int aspect) {
      method setFullScreen (line 295) | public Builder setFullScreen(boolean isFullScreen) {
      method setVideoRecord (line 305) | public Builder setVideoRecord(boolean isSupportVideoRecord) {
      method setJustVideoRecord (line 315) | public Builder setJustVideoRecord(boolean isJustVideoRecord) {
      method setMaxRecordDuration (line 325) | public Builder setMaxRecordDuration(long maxRecordDuration) {
      method setMinRecordDuration (line 335) | public Builder setMinRecordDuration(long minimumDuration) {
      method setRecordProgressColor (line 343) | public Builder setRecordProgressColor(@ColorInt int colorRecordProgr...
      method setRenderer (line 351) | public Builder setRenderer(@NonNull Class<? extends IPreviewer.Rende...
      method setRecordResolution (line 365) | public Builder setRecordResolution(@Options.Video.Resolution int rec...
      method setCropConfig (line 373) | public Builder setCropConfig(@Nullable CropperConfig cropConfig) {
      method build (line 378) | public TakerConfig build() {
    method TakerConfig (line 174) | private TakerConfig() {
    method rebuild (line 177) | public Builder rebuild() {
    method getQuality (line 181) | public int getQuality() {
    method getRelativePath (line 185) | public String getRelativePath() {
    method getPreviewAspect (line 189) | public int getPreviewAspect() {
    method isFullScreen (line 193) | public boolean isFullScreen() {
    method isSupportVideoRecord (line 197) | public boolean isSupportVideoRecord() {
    method getMaximumDuration (line 201) | public long getMaximumDuration() {
    method getMinimumDuration (line 205) | public long getMinimumDuration() {
    method getRecordProgressColor (line 209) | public int getRecordProgressColor() {
    method getRendererClassName (line 213) | public String getRendererClassName() {
    method getRecordResolution (line 217) | public int getRecordResolution() {
    method getAuthority (line 221) | public String getAuthority() {
    method getCropConfig (line 225) | public CropperConfig getCropConfig() {
    method getRendererClsName (line 229) | public String getRendererClsName() {
    method isJustVideoRecord (line 233) | public boolean isJustVideoRecord() {
    class Builder (line 237) | public static class Builder {
      method Builder (line 241) | private Builder() {
      method Builder (line 245) | private Builder(@NonNull TakerConfig config) {
      method setRelativePath (line 262) | public Builder setRelativePath(@Nullable String relativePath) {
      method setAuthority (line 270) | public Builder setAuthority(@NonNull String authority) {
      method setPictureQuality (line 279) | public Builder setPictureQuality(int quality) {
      method setPreviewAspect (line 287) | public Builder setPreviewAspect(@Aspect int aspect) {
      method setFullScreen (line 295) | public Builder setFullScreen(boolean isFullScreen) {
      method setVideoRecord (line 305) | public Builder setVideoRecord(boolean isSupportVideoRecord) {
      method setJustVideoRecord (line 315) | public Builder setJustVideoRecord(boolean isJustVideoRecord) {
      method setMaxRecordDuration (line 325) | public Builder setMaxRecordDuration(long maxRecordDuration) {
      method setMinRecordDuration (line 335) | public Builder setMinRecordDuration(long minimumDuration) {
      method setRecordProgressColor (line 343) | public Builder setRecordProgressColor(@ColorInt int colorRecordProgr...
      method setRenderer (line 351) | public Builder setRenderer(@NonNull Class<? extends IPreviewer.Rende...
      method setRecordResolution (line 365) | public Builder setRecordResolution(@Options.Video.Resolution int rec...
      method setCropConfig (line 373) | public Builder setCropConfig(@Nullable CropperConfig cropConfig) {
      method build (line 378) | public TakerConfig build() {

FILE: lib-album/src/main/taker/com/sharry/lib/album/TakerManager.java
  class TakerManager (line 19) | public class TakerManager {
    method with (line 28) | public static TakerManager with(@NonNull Context context) {
    method TakerManager (line 40) | private TakerManager(Activity activity) {
    method setConfig (line 47) | public TakerManager setConfig(@NonNull TakerConfig config) {
    method take (line 55) | public void take(@NonNull final TakerCallbackLambda callbackLambda) {
    method take (line 72) | public void take(@NonNull final TakerCallback callback) {
    method takeActual (line 87) | private void takeActual(final TakerCallback callback) {
    method performCropPicture (line 118) | private void performCropPicture(MediaMeta mediaMeta, final TakerCallba...

FILE: lib-album/src/main/taker/com/sharry/lib/album/TakerPresenter.java
  class TakerPresenter (line 32) | class TakerPresenter implements ITakerContract.IPresenter {
    method TakerPresenter (line 48) | TakerPresenter(TakerActivity view, TakerConfig config) {
    method handleVideoPlayFailed (line 83) | @Override
    method handleTakePicture (line 94) | @Override
    method handleRecordStart (line 110) | @Override
    method handleRecordFinish (line 116) | @Override
    method handleGranted (line 128) | @Override
    method handleDenied (line 137) | @Override
    method handleViewDestroy (line 144) | @Override
    method setupViews (line 153) | private void setupViews() {
    method performProgressChanged (line 182) | private void performProgressChanged(long time) {
    method performRecordFiled (line 190) | private void performRecordFiled() {
    method performRecordComplete (line 199) | private void performRecordComplete(Uri uri, File file) {
    method performPictureEnsure (line 209) | private void performPictureEnsure() {
    method performVideoEnsure (line 241) | private void performVideoEnsure() {
    method recycle (line 252) | private void recycle() {

FILE: lib-album/src/main/utils/com/sharry/lib/album/ActivityStateUtil.java
  class ActivityStateUtil (line 15) | class ActivityStateUtil {
    method isIllegalState (line 19) | static boolean isIllegalState(Activity activity) {
    method fixRequestOrientation (line 27) | static void fixRequestOrientation(Activity activity) {

FILE: lib-album/src/main/utils/com/sharry/lib/album/CallbackFragment.java
  class CallbackFragment (line 18) | public class CallbackFragment extends Fragment {
    method getInstance (line 28) | @Nullable
    method findFragmentFromActivity (line 48) | private static CallbackFragment findFragmentFromActivity(@NonNull Acti...
    method newInstance (line 52) | private static CallbackFragment newInstance() {
    method onCreate (line 58) | @Override
    method onActivityResult (line 64) | @Override
    method setCallback (line 75) | public void setCallback(Callback callback) {
    type Callback (line 82) | public interface Callback {
      method onActivityResult (line 84) | void onActivityResult(int requestCode, int resultCode, Intent data);

FILE: lib-album/src/main/utils/com/sharry/lib/album/ColorUtil.java
  class ColorUtil (line 13) | class ColorUtil {
    method gradualChanged (line 22) | static int gradualChanged(float fraction, @ColorInt int colorStart, @C...
    method alphaColor (line 62) | static int alphaColor(int baseColor, @FloatRange(from = 0f, to = 1f) f...

FILE: lib-album/src/main/utils/com/sharry/lib/album/CompressUtil.java
  class CompressUtil (line 20) | class CompressUtil {
    method doCompress (line 25) | static void doCompress(String originPath, FileDescriptor fd, int quali...
    method doCompress (line 44) | static void doCompress(Bitmap originBitmap, FileDescriptor fd, int qua...
    method getBitmapOptions (line 60) | private static BitmapFactory.Options getBitmapOptions(String filePath) {
    method calculateSampleSize (line 76) | private static int calculateSampleSize(int srcWidth, int srcHeight) {
    method qualityCompress (line 109) | static void qualityCompress(Bitmap srcBitmap, int quality, FileDescrip...
    method rotateBitmap (line 124) | private static Bitmap rotateBitmap(Bitmap bitmap, int angle) {
    method readPictureAngle (line 141) | private static int readPictureAngle(String path) throws IOException {

FILE: lib-album/src/main/utils/com/sharry/lib/album/Constants.java
  type Constants (line 8) | interface Constants {

FILE: lib-album/src/main/utils/com/sharry/lib/album/DateUtil.java
  class DateUtil (line 12) | class DateUtil {
    method format (line 22) | static String format(long duration) {

FILE: lib-album/src/main/utils/com/sharry/lib/album/DensityUtil.java
  class DensityUtil (line 11) | class DensityUtil {
    method dp2px (line 16) | static int dp2px(Context context, float dp) {

FILE: lib-album/src/main/utils/com/sharry/lib/album/FileUtil.java
  class FileUtil (line 32) | class FileUtil {
    method getParentFolderPath (line 39) | static String getParentFolderPath(String filePath) {
    method getLastFileName (line 53) | static String getLastFileName(String filePath) {
    method getUriFromFile (line 60) | static Uri getUriFromFile(Context context, String authority, File file) {
    method createTempJpegFile (line 70) | static File createTempJpegFile(Context context) {
    method createJpegPendingItem (line 94) | @TargetApi(29)
    method publishPendingItem (line 120) | @TargetApi(29)
    method delete (line 133) | @TargetApi(29)
    method createJpegFile (line 143) | static File createJpegFile(Context context, String relativePath) {
    method notifyMediaStore (line 169) | static void notifyMediaStore(Context context, String filePath) {
    method delete (line 183) | static void delete(Context context, File file) {
    method getImagePath (line 194) | static String getImagePath(final Context context, final Uri uri) {
    method getVideoPath (line 226) | static String getVideoPath(final Context context, final Uri uri) {

FILE: lib-album/src/main/utils/com/sharry/lib/album/PermissionsCallback.java
  type PermissionsCallback (line 10) | interface PermissionsCallback {
    method onResult (line 12) | void onResult(boolean granted);

FILE: lib-album/src/main/utils/com/sharry/lib/album/PermissionsFragment.java
  class PermissionsFragment (line 23) | public class PermissionsFragment extends Fragment {
    method getInstance (line 30) | public static PermissionsFragment getInstance() {
    method onCreate (line 34) | @Override
    method requestPermissions (line 40) | @TargetApi(Build.VERSION_CODES.M)
    method onRequestPermissionsResult (line 47) | @Override
    method isGranted (line 68) | @TargetApi(Build.VERSION_CODES.M)
    method isRevoked (line 73) | @TargetApi(Build.VERSION_CODES.M)
    method showPermissionDeniedDialog (line 78) | @TargetApi(Build.VERSION_CODES.M)
    method onActivityResult (line 104) | @Override
    method log (line 119) | void log(String message) {

FILE: lib-album/src/main/utils/com/sharry/lib/album/PermissionsHelper.java
  class PermissionsHelper (line 20) | class PermissionsHelper {
    method with (line 26) | public static PermissionsHelper with(Context context) {
    method PermissionsHelper (line 35) | private PermissionsHelper(Activity activity) {
    method request (line 42) | PermissionsHelper request(String... permissions) {
    method requestArray (line 49) | PermissionsHelper requestArray(String[] permissions) {
    method execute (line 58) | void execute(PermissionsCallback permissionsCallback) {
    method isGranted (line 68) | boolean isGranted(String permission) {
    method isRevoked (line 77) | @SuppressWarnings("WeakerAccess")
    method getPermissionsFragment (line 85) | private PermissionsFragment getPermissionsFragment(Activity activity) {
    method findPermissionsFragment (line 102) | private PermissionsFragment findPermissionsFragment(Activity activity) {
    method ensure (line 110) | private void ensure(String[] permissions) {
    method executeActual (line 119) | private void executeActual(String[] permissions, PermissionsCallback c...
    method isMarshmallow (line 149) | private boolean isMarshmallow() {

FILE: lib-album/src/main/utils/com/sharry/lib/album/Preconditions.java
  class Preconditions (line 16) | final class Preconditions {
    method Preconditions (line 18) | private Preconditions() {
    method checkArgument (line 22) | public static void checkArgument(boolean expression, @NonNull String m...
    method checkNotNull (line 28) | @NonNull
    method checkNotNull (line 33) | @NonNull
    method checkNotEmpty (line 41) | @NonNull
    method checkNotEmpty (line 49) | @NonNull

FILE: lib-album/src/main/utils/com/sharry/lib/album/SharedElementHelper.java
  class SharedElementHelper (line 35) | class SharedElementHelper {
    method set (line 46) | @Override
    method get (line 51) | @Override
    method set (line 59) | @Override
    method get (line 64) | @Override
    method set (line 72) | @Override
    method get (line 77) | @Override
    method createSharedElementEnterAnimator (line 89) | static Animator createSharedElementEnterAnimator(View target, Bounds d...
    method createSharedElementExitAnimator (line 112) | static Animator createSharedElementExitAnimator(@Nullable ImageView ta...
    method getBoundsChangedAnim (line 142) | private static AnimatorSet getBoundsChangedAnim(View target, Bounds da...
    class ViewBounds (line 166) | private static class ViewBounds {
      method ViewBounds (line 176) | ViewBounds(View view) {
      method setTopLeft (line 180) | void setTopLeft(PointF topLeft) {
      method setBottomRight (line 189) | void setBottomRight(PointF bottomRight) {
      method setLeftTopRightBottom (line 198) | private void setLeftTopRightBottom() {
    class PathProperty (line 209) | private static class PathProperty<T> extends Property<T, Float> {
      method PathProperty (line 218) | PathProperty(Property<T, PointF> property, Path path) {
      method get (line 225) | @Override
      method set (line 230) | @Override
    method centerCropMatrix (line 245) | private static Matrix centerCropMatrix(int startWidth, int startHeight...
    class MatrixEvaluator (line 264) | public static class MatrixEvaluator implements TypeEvaluator<Matrix> {
      method evaluate (line 272) | @Override
    class Bounds (line 290) | static class Bounds implements Parcelable {
      method parseFrom (line 292) | static Bounds parseFrom(@NonNull View sharedElement, int positionInP...
      method Bounds (line 310) | private Bounds() {
      method Bounds (line 314) | Bounds(Parcel in) {
      method writeToParcel (line 322) | @Override
      method describeContents (line 331) | @Override
      method toString (line 336) | @Override
      method createFromParcel (line 348) | @Override
      method newArray (line 353) | @Override

FILE: lib-album/src/main/utils/com/sharry/lib/album/VersionUtil.java
  class VersionUtil (line 12) | class VersionUtil {
    method isJellyBeanMr1 (line 14) | static boolean isJellyBeanMr1() {
    method isLollipop (line 18) | static boolean isLollipop() {
    method isQ (line 22) | static boolean isQ() {

FILE: lib-album/src/main/watcher/com/sharry/lib/album/DisplayAdapter.java
  class DisplayAdapter (line 16) | class DisplayAdapter extends FragmentStatePagerAdapter {
    method DisplayAdapter (line 20) | DisplayAdapter(FragmentManager fragmentManager, List<? extends MediaMe...
    method getItem (line 25) | @Override
    method getItemPosition (line 32) | @Override
    method getCount (line 37) | @Override

FILE: lib-album/src/main/watcher/com/sharry/lib/album/PickedPanelAdapter.java
  class PickedPanelAdapter (line 19) | class PickedPanelAdapter extends RecyclerView.Adapter<PickedPanelAdapter...
    method PickedPanelAdapter (line 24) | PickedPanelAdapter(ArrayList<MediaMeta> userPickedSet, Interaction int...
    method onCreateViewHolder (line 29) | @NonNull
    method onBindViewHolder (line 40) | @Override
    method getItemCount (line 45) | @Override
    type Interaction (line 50) | public interface Interaction {
      method onPreviewItemClicked (line 52) | void onPreviewItemClicked(ImageView imageView, MediaMeta meta, int p...
    class ViewHolder (line 56) | class ViewHolder extends RecyclerView.ViewHolder implements View.OnCli...
      method ViewHolder (line 60) | ViewHolder(View itemView) {
      method onClick (line 66) | @Override

FILE: lib-album/src/main/watcher/com/sharry/lib/album/WatcherActivity.java
  class WatcherActivity (line 40) | public class WatcherActivity extends AppCompatActivity implements
    method launchActivityForResult (line 67) | static void launchActivityForResult(@NonNull Activity request, @NonNul...
    method onCreate (line 109) | @Override
    method initTitle (line 119) | private void initTitle() {
    method initViews (line 138) | private void initViews() {
    method initPresenter (line 159) | private void initPresenter() {
    method onBackPressed (line 167) | @Override
    method finish (line 181) | @Override
    method onDestroy (line 188) | @Override
    method showSharedElementEnter (line 198) | @Override
    method showSharedElementExitAndFinish (line 222) | @Override
    method setLeftTitleText (line 246) | @Override
    method setIndicatorText (line 251) | @Override
    method setIndicatorColors (line 256) | @Override
    method setIndicatorVisible (line 264) | @Override
    method setIndicatorChecked (line 269) | @Override
    method setEnsureText (line 274) | @Override
    method setDisplayAdapter (line 279) | @Override
    method displayAt (line 285) | @Override
    method setPickedAdapter (line 290) | @Override
    method notifyItemRemoved (line 295) | @Override
    method notifyItemPicked (line 306) | @Override
    method showPickedPanel (line 317) | @Override
    method dismissPickedPanel (line 336) | @Override
    method pickedPanelSmoothScrollToPosition (line 355) | @Override
    method showMsg (line 360) | @Override
    method sendEnsureBroadcast (line 365) | @Override
    method onPagerChanged (line 373) | @Override
    method handleDismissAction (line 380) | @Override
    method onDismissed (line 385) | @Override
    method onPreviewItemClicked (line 392) | @Override

FILE: lib-album/src/main/watcher/com/sharry/lib/album/WatcherCallback.java
  type WatcherCallback (line 13) | public interface WatcherCallback {
    method onWatcherPickedComplete (line 17) | @Override
    method onWatcherPickedFailed (line 22) | @Override
    method onWatcherPickedComplete (line 31) | void onWatcherPickedComplete(@NonNull ArrayList<MediaMeta> pickedSet);
    method onWatcherPickedFailed (line 36) | void onWatcherPickedFailed();

FILE: lib-album/src/main/watcher/com/sharry/lib/album/WatcherCallbackLambda.java
  type WatcherCallbackLambda (line 14) | public interface WatcherCallbackLambda {
    method onWatcherPicked (line 16) | void onWatcherPicked(@Nullable ArrayList<MediaMeta> pickedSet);

FILE: lib-album/src/main/watcher/com/sharry/lib/album/WatcherConfig.java
  class WatcherConfig (line 20) | public class WatcherConfig {
    method Builder (line 22) | public static Builder Builder() {
      method Builder (line 121) | private Builder() {
      method Builder (line 125) | private Builder(@NonNull WatcherConfig config) {
      method setThreshold (line 132) | public Builder setThreshold(int threshold) {
      method setDisplayDataSet (line 143) | public Builder setDisplayDataSet(@NonNull ArrayList<MediaMeta> metas...
      method setUserPickedSet (line 156) | public Builder setUserPickedSet(@Nullable ArrayList<MediaMeta> picke...
      method setIndicatorTextColor (line 166) | public Builder setIndicatorTextColor(@ColorInt int textColor) {
      method setIndicatorSolidColor (line 176) | public Builder setIndicatorSolidColor(@ColorInt int solidColor) {
      method setIndicatorBorderColor (line 187) | public Builder setIndicatorBorderColor(@ColorInt int checkedColor, @...
      method build (line 193) | public WatcherConfig build() {
    method WatcherConfig (line 72) | public WatcherConfig() {
    method getPictureUris (line 75) | @NonNull
    method getUserPickedSet (line 80) | @Nullable
    method getThreshold (line 85) | public int getThreshold() {
    method getIndicatorTextColor (line 89) | public int getIndicatorTextColor() {
    method getIndicatorSolidColor (line 93) | public int getIndicatorSolidColor() {
    method getIndicatorBorderCheckedColor (line 97) | public int getIndicatorBorderCheckedColor() {
    method getIndicatorBorderUncheckedColor (line 101) | public int getIndicatorBorderUncheckedColor() {
    method getPosition (line 105) | public int getPosition() {
    method isPickerSupport (line 109) | public boolean isPickerSupport() {
    method rebuild (line 113) | public Builder rebuild() {
    class Builder (line 117) | public static class Builder {
      method Builder (line 121) | private Builder() {
      method Builder (line 125) | private Builder(@NonNull WatcherConfig config) {
      method setThreshold (line 132) | public Builder setThreshold(int threshold) {
      method setDisplayDataSet (line 143) | public Builder setDisplayDataSet(@NonNull ArrayList<MediaMeta> metas...
      method setUserPickedSet (line 156) | public Builder setUserPickedSet(@Nullable ArrayList<MediaMeta> picke...
      method setIndicatorTextColor (line 166) | public Builder setIndicatorTextColor(@ColorInt int textColor) {
      method setIndicatorSolidColor (line 176) | public Builder setIndicatorSolidColor(@ColorInt int solidColor) {
      method setIndicatorBorderColor (line 187) | public Builder setIndicatorBorderColor(@ColorInt int checkedColor, @...
      method build (line 193) | public WatcherConfig build() {

FILE: lib-album/src/main/watcher/com/sharry/lib/album/WatcherContract.java
  type WatcherContract (line 15) | interface WatcherContract {
    type IView (line 17) | interface IView {
      method showSharedElementEnter (line 19) | void showSharedElementEnter(@NonNull MediaMeta mediaMeta, @NonNull S...
      method showSharedElementExitAndFinish (line 21) | void showSharedElementExitAndFinish(@NonNull SharedElementHelper.Bou...
      method setLeftTitleText (line 23) | void setLeftTitleText(@NonNull CharSequence content);
      method setIndicatorVisible (line 25) | void setIndicatorVisible(boolean isShowCheckedIndicator);
      method setIndicatorColors (line 27) | void setIndicatorColors(int indicatorBorderCheckedColor, int indicat...
      method setIndicatorChecked (line 30) | void setIndicatorChecked(boolean isChecked);
      method setIndicatorText (line 32) | void setIndicatorText(@NonNull CharSequence indicatorText);
      method setEnsureText (line 34) | void setEnsureText(@NonNull CharSequence content);
      method setDisplayAdapter (line 36) | void setDisplayAdapter(@NonNull ArrayList<MediaMeta> mediaMetas);
      method displayAt (line 38) | void displayAt(int position);
      method setPickedAdapter (line 40) | void setPickedAdapter(@NonNull ArrayList<MediaMeta> pickedSet);
      method pickedPanelSmoothScrollToPosition (line 42) | void pickedPanelSmoothScrollToPosition(int position);
      method showPickedPanel (line 44) | void showPickedPanel();
      method dismissPickedPanel (line 46) | void dismissPickedPanel();
      method notifyItemRemoved (line 48) | void notifyItemRemoved(@NonNull MediaMeta removedMeta, int removedIn...
      method notifyItemPicked (line 50) | void notifyItemPicked(@NonNull MediaMeta addedMeta, int addedIndex);
      method getString (line 52) | String getString(@StringRes int resId);
      method showMsg (line 54) | void showMsg(@NonNull String msg);
      method sendEnsureBroadcast (line 56) | void sendEnsureBroadcast();
      method finish (line 58) | void finish();
    type IPresenter (line 61) | interface IPresenter {
      method handlePagerChanged (line 63) | void handlePagerChanged(int position);
      method handleEnsureClicked (line 65) | void handleEnsureClicked();
      method handleIndicatorClick (line 67) | void handleIndicatorClick(boolean isChecked);
      method handlePickedItemClicked (line 69) | void handlePickedItemClicked(MediaMeta pickedUri);
      method handleDisplayPagerDismiss (line 71) | boolean handleDisplayPagerDismiss();
      method getExitSharedElement (line 73) | SharedElementHelper.Bounds getExitSharedElement();

FILE: lib-album/src/main/watcher/com/sharry/lib/album/WatcherFragment.java
  class WatcherFragment (line 24) | public class WatcherFragment extends Fragment implements View.OnClickLis...
    method getInstance (line 29) | @NonNull
    method onCreateView (line 55) | @Nullable
    method onViewCreated (line 61) | @Override
    method onDestroyView (line 67) | @Override
    method onClick (line 81) | @Override
    method setDataSource (line 88) | void setDataSource(@Nullable MediaMeta mediaMeta) {
    method initView (line 93) | private void initView(View view) {
    method performShowDataSource (line 101) | private void performShowDataSource() {
    method getPhotoView (line 122) | PhotoView getPhotoView() {
    method dismissOtherView (line 129) | void dismissOtherView() {

FILE: lib-album/src/main/watcher/com/sharry/lib/album/WatcherManager.java
  class WatcherManager (line 20) | public class WatcherManager {
    method with (line 28) | public static WatcherManager with(@NonNull Context context) {
    method WatcherManager (line 41) | private WatcherManager(Activity activity) {
    method setSharedElement (line 48) | public WatcherManager setSharedElement(@NonNull View transitionView) {
    method setConfig (line 56) | public WatcherManager setConfig(@NonNull WatcherConfig config) {
    method setLoaderEngine (line 64) | public WatcherManager setLoaderEngine(@NonNull ILoaderEngine loader) {
    method start (line 72) | public void start() {
    method startForResult (line 79) | public void startForResult(@NonNull final WatcherCallbackLambda callba...
    method startForResult (line 97) | public void startForResult(@NonNull final WatcherCallback callback) {
    method startForResultActual (line 115) | private void startForResultActual(final WatcherCallback callback) {

FILE: lib-album/src/main/watcher/com/sharry/lib/album/WatcherPresenter.java
  class WatcherPresenter (line 16) | class WatcherPresenter implements WatcherContract.IPresenter {
    method WatcherPresenter (line 29) | WatcherPresenter(WatcherContract.IView view, WatcherConfig config, Sha...
    method setupViews (line 44) | private void setupViews() {
    method handlePagerChanged (line 83) | @Override
    method handleIndicatorClick (line 99) | @Override
    method handlePickedItemClicked (line 137) | @Override
    method handleEnsureClicked (line 145) | @Override
    method handleDisplayPagerDismiss (line 155) | @Override
    method getExitSharedElement (line 168) | @Override
    method buildToolbarLeftText (line 180) | private CharSequence buildToolbarLeftText() {
    method buildToolbarCheckedIndicatorText (line 187) | private CharSequence buildToolbarCheckedIndicatorText() {
    method buildEnsureText (line 194) | private CharSequence buildEnsureText() {

FILE: lib-album/src/main/widget/com/sharry/lib/album/CheckedIndicatorView.java
  class CheckedIndicatorView (line 29) | public class CheckedIndicatorView extends AppCompatTextView {
    method CheckedIndicatorView (line 52) | public CheckedIndicatorView(Context context) {
    method CheckedIndicatorView (line 56) | public CheckedIndicatorView(Context context, AttributeSet attrs) {
    method CheckedIndicatorView (line 60) | public CheckedIndicatorView(Context context, AttributeSet attrs, int d...
    method setOnClickListener (line 66) | @Override
    method onMeasure (line 78) | @Override
    method onDraw (line 96) | @Override
    method setChecked (line 112) | public void setChecked(boolean isChecked) {
    method setCheckedWithoutAnimator (line 122) | public void setCheckedWithoutAnimator(boolean isChecked) {
    method isChecked (line 128) | public boolean isChecked() {
    method setBorderColor (line 135) | public void setBorderColor(@ColorInt int checkedColor, @ColorInt int u...
    method setSolidColor (line 147) | public void setSolidColor(@ColorInt int solidColor) {
    method setTextSize (line 156) | public void setTextSize(int dip) {
    method init (line 160) | private void init() {
    method executeAnimator (line 181) | private void executeAnimator(final boolean destIsChecked) {

FILE: lib-album/src/main/widget/com/sharry/lib/album/DraggableViewPager.java
  class DraggableViewPager (line 34) | public class DraggableViewPager extends ViewPager {
    method DraggableViewPager (line 53) | public DraggableViewPager(Context context) {
    method DraggableViewPager (line 57) | public DraggableViewPager(Context context, AttributeSet attrs) {
    method init (line 62) | private void init() {
    method setCallback (line 91) | public void setCallback(@Nullable Callback listener) {
    method setBackgroundColorRes (line 100) | public void setBackgroundColorRes(@ColorRes int colorResId) {
    method setBackgroundColor (line 104) | @Override
    method onInterceptTouchEvent (line 110) | @Override
    method onTouchEvent (line 142) | @Override
    method setAdapter (line 181) | @Override
    method getCurrentView (line 186) | @Nullable
    method recover (line 195) | private void recover() {
    method dismiss (line 227) | private void dismiss() {
    method getBackgroundColor (line 269) | private int getBackgroundColor() {
    type Callback (line 273) | public interface Callback {
      method onPagerChanged (line 275) | void onPagerChanged(int position);
      method handleDismissAction (line 280) | boolean handleDismissAction();
      method onDismissed (line 282) | void onDismissed();
    class PagerAdapterProxy (line 286) | private static final class PagerAdapterProxy extends PagerAdapter {
      method PagerAdapterProxy (line 291) | PagerAdapterProxy(PagerAdapter originAdapter) {
      method getCurrentView (line 295) | public View getCurrentView() {
      method getCount (line 299) | @Override
      method isViewFromObject (line 304) | @Override
      method instantiateItem (line 309) | @Override
      method destroyItem (line 314) | @Override
      method getItemPosition (line 319) | @Override
      method setPrimaryItem (line 324) | @Override
      method setPrimaryItem (line 334) | @Override
      method startUpdate (line 344) | @Override
      method finishUpdate (line 349) | @Override
      method startUpdate (line 354) | @Override
      method instantiateItem (line 359) | @NonNull
      method destroyItem (line 365) | @Override
      method finishUpdate (line 370) | @Override
      method saveState (line 375) | @Nullable
      method restoreState (line 381) | @Override
      method getPageTitle (line 386) | @Nullable
      method getPageWidth (line 392) | @Override
      method notifyDataSetChanged (line 397) | @Override
      method registerDataSetObserver (line 402) | @Override
      method unregisterDataSetObserver (line 407) | @Override

FILE: lib-album/src/main/widget/com/sharry/lib/album/PicturePickerFabBehavior.java
  class PicturePickerFabBehavior (line 25) | public class PicturePickerFabBehavior extends CoordinatorLayout.Behavior...
    method from (line 27) | public static PicturePickerFabBehavior from(View view) {
    method PicturePickerFabBehavior (line 45) | public PicturePickerFabBehavior() {
    method PicturePickerFabBehavior (line 48) | public PicturePickerFabBehavior(Context context, AttributeSet attrs) {
    method setBehaviorValid (line 57) | public void setBehaviorValid(boolean isValid) {
    method layoutDependsOn (line 64) | @Override
    method onStartNestedScroll (line 69) | @Override
    method onNestedScroll (line 77) | @Override
    method setAnimator (line 107) | private void setAnimator(View target, final boolean isUp) {
    method getAppearAnimator (line 123) | private AnimatorSet getAppearAnimator(final View target) {
    method getDismissAnimator (line 144) | private AnimatorSet getDismissAnimator(final View target) {

FILE: lib-album/src/main/widget/com/sharry/lib/album/RecorderButton.java
  class RecorderButton (line 33) | public class RecorderButton extends View implements View.OnTouchListener...
    method handleMessage (line 80) | @Override
    method RecorderButton (line 95) | public RecorderButton(Context context) {
    method RecorderButton (line 99) | public RecorderButton(Context context, @Nullable AttributeSet attrs) {
    method RecorderButton (line 103) | public RecorderButton(Context context, @Nullable AttributeSet attrs, i...
    method dispatchTouchEvent (line 119) | @Override
    method onTouch (line 124) | @Override
    method onClick (line 143) | @Override
    method onMeasure (line 148) | @Override
    method onDraw (line 168) | @Override
    method setProgressColor (line 198) | public void setProgressColor(@ColorInt int color) {
    method setLongClickEnable (line 205) | public void setLongClickEnable(boolean isLongClickEnable) {
    method setMaxProgress (line 212) | public void setMaxProgress(long maxDuration) {
    method setCurrentProgress (line 219) | public void setCurrentProgress(long curDuration) {
    method handleRecordStart (line 243) | private void handleRecordStart() {
    method handleRecordFinish (line 256) | private void handleRecordFinish() {
    method createStartAnim (line 272) | private AnimatorSet createStartAnim() {
    method createFinishAnim (line 312) | private AnimatorSet createFinishAnim() {
    type Interaction (line 367) | public interface Interaction {
      method onTakePicture (line 372) | void onTakePicture();
      method onRecordStart (line 377) | void onRecordStart();
      method onRecordFinish (line 384) | void onRecordFinish(long duration);

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/Compat.java
  class Compat (line 23) | class Compat {
    method postOnAnimation (line 27) | public static void postOnAnimation(View view, Runnable runnable) {
    method postOnAnimationJellyBean (line 35) | @TargetApi(16)

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/CustomGestureDetector.java
  class CustomGestureDetector (line 27) | class CustomGestureDetector {
    method CustomGestureDetector (line 43) | CustomGestureDetector(Context context, OnGestureListener listener) {
    method getActiveX (line 77) | private float getActiveX(MotionEvent ev) {
    method getActiveY (line 85) | private float getActiveY(MotionEvent ev) {
    method isScaling (line 93) | public boolean isScaling() {
    method isDragging (line 97) | public boolean isDragging() {
    method onTouchEvent (line 101) | public boolean onTouchEvent(MotionEvent ev) {
    method processTouchEvent (line 111) | private boolean processTouchEvent(MotionEvent ev) {

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/OnGestureListener.java
  type OnGestureListener (line 18) | interface OnGestureListener {
    method onDrag (line 20) | void onDrag(float dx, float dy);
    method onFling (line 22) | void onFling(float startX, float startY, float velocityX,
    method onScale (line 25) | void onScale(float scaleFactor, float focusX, float focusY);

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/OnMatrixChangedListener.java
  type OnMatrixChangedListener (line 9) | public interface OnMatrixChangedListener {
    method onMatrixChanged (line 17) | void onMatrixChanged(RectF rect);

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/OnOutsidePhotoTapListener.java
  type OnOutsidePhotoTapListener (line 8) | public interface OnOutsidePhotoTapListener {
    method onOutsidePhotoTap (line 13) | void onOutsidePhotoTap(ImageView imageView);

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/OnPhotoTapListener.java
  type OnPhotoTapListener (line 9) | public interface OnPhotoTapListener {
    method onPhotoTap (line 21) | void onPhotoTap(ImageView view, float x, float y);

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/OnScaleChangedListener.java
  type OnScaleChangedListener (line 7) | public interface OnScaleChangedListener {
    method onScaleChange (line 16) | void onScaleChange(float scaleFactor, float focusX, float focusY);

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/OnSingleFlingListener.java
  type OnSingleFlingListener (line 9) | public interface OnSingleFlingListener {
    method onFling (line 20) | boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float...

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/OnViewDragListener.java
  type OnViewDragListener (line 6) | public interface OnViewDragListener {
    method onDrag (line 15) | void onDrag(float dx, float dy);

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/OnViewTapListener.java
  type OnViewTapListener (line 5) | public interface OnViewTapListener {
    method onViewTap (line 15) | void onViewTap(View view, float x, float y);

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/PhotoView.java
  class PhotoView (line 32) | public class PhotoView extends AppCompatImageView {
    method PhotoView (line 37) | public PhotoView(Context context) {
    method PhotoView (line 41) | public PhotoView(Context context, AttributeSet attr) {
    method PhotoView (line 45) | public PhotoView(Context context, AttributeSet attr, int defStyle) {
    method init (line 50) | private void init() {
    method getAttacher (line 69) | public PhotoViewAttacher getAttacher() {
    method getScaleType (line 73) | @Override
    method getImageMatrix (line 78) | @Override
    method setOnLongClickListener (line 83) | @Override
    method setOnClickListener (line 88) | @Override
    method setScaleType (line 93) | @Override
    method setImageDrawable (line 102) | @Override
    method setImageResource (line 111) | @Override
    method setImageURI (line 119) | @Override
    method setFrame (line 127) | @Override
    method animateTransform (line 139) | public void animateTransform(Matrix matrix) {
    method setRotationTo (line 152) | public void setRotationTo(float rotationDegree) {
    method setRotationBy (line 156) | public void setRotationBy(float rotationDegree) {
    method isZoomEnabled (line 160) | @Deprecated
    method isZoomable (line 165) | public boolean isZoomable() {
    method setZoomable (line 169) | public void setZoomable(boolean zoomable) {
    method getDisplayRect (line 173) | public RectF getDisplayRect() {
    method getDisplayMatrix (line 177) | public void getDisplayMatrix(Matrix matrix) {
    method setDisplayMatrix (line 181) | public boolean setDisplayMatrix(Matrix finalRectangle) {
    method getSuppMatrix (line 185) | public void getSuppMatrix(Matrix matrix) {
    method setSuppMatrix (line 189) | public boolean setSuppMatrix(Matrix matrix) {
    method getMinimumScale (line 193) | public float getMinimumScale() {
    method getMediumScale (line 197) | public float getMediumScale() {
    method getMaximumScale (line 201) | public float getMaximumScale() {
    method getScale (line 205) | public float getScale() {
    method setAllowParentInterceptOnEdge (line 209) | public void setAllowParentInterceptOnEdge(boolean allow) {
    method setMinimumScale (line 213) | public void setMinimumScale(float minimumScale) {
    method setMediumScale (line 217) | public void setMediumScale(float mediumScale) {
    method setMaximumScale (line 221) | public void setMaximumScale(float maximumScale) {
    method setScaleLevels (line 225) | public void setScaleLevels(float minimumScale, float mediumScale, floa...
    method setOnMatrixChangeListener (line 229) | public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
    method setOnPhotoTapListener (line 233) | public void setOnPhotoTapListener(OnPhotoTapListener listener) {
    method setOnOutsidePhotoTapListener (line 237) | public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener lis...
    method setOnViewTapListener (line 241) | public void setOnViewTapListener(OnViewTapListener listener) {
    method setOnViewDragListener (line 245) | public void setOnViewDragListener(OnViewDragListener listener) {
    method setScale (line 249) | public void setScale(float scale) {
    method setScale (line 253) | public void setScale(float scale, boolean animate) {
    method setScale (line 257) | public void setScale(float scale, float focalX, float focalY, boolean ...
    method setZoomTransitionDuration (line 261) | public void setZoomTransitionDuration(int milliseconds) {
    method setOnDoubleTapListener (line 265) | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener...
    method setOnScaleChangeListener (line 269) | public void setOnScaleChangeListener(OnScaleChangedListener onScaleCha...
    method setOnSingleFlingListener (line 273) | public void setOnSingleFlingListener(OnSingleFlingListener onSingleFli...

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/PhotoViewAttacher.java
  class PhotoViewAttacher (line 40) | public class PhotoViewAttacher implements View.OnTouchListener,
    method onDrag (line 95) | @Override
    method onFling (line 132) | @Override
    method onScale (line 140) | @Override
    method PhotoViewAttacher (line 152) | public PhotoViewAttacher(ImageView imageView) {
    method setOnDoubleTapListener (line 261) | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener...
    method setOnScaleChangeListener (line 265) | public void setOnScaleChangeListener(OnScaleChangedListener onScaleCha...
    method setOnSingleFlingListener (line 269) | public void setOnSingleFlingListener(OnSingleFlingListener onSingleFli...
    method isZoomEnabled (line 273) | @Deprecated
    method getDisplayRect (line 278) | public RectF getDisplayRect() {
    method setDisplayMatrix (line 283) | public boolean setDisplayMatrix(Matrix finalMatrix) {
    method setBaseRotation (line 298) | public void setBaseRotation(final float degrees) {
    method setRotationTo (line 305) | public void setRotationTo(float degrees) {
    method setRotationBy (line 310) | public void setRotationBy(float degrees) {
    method getMinimumScale (line 315) | public float getMinimumScale() {
    method getMediumScale (line 319) | public float getMediumScale() {
    method getMaximumScale (line 323) | public float getMaximumScale() {
    method getScale (line 327) | public float getScale() {
    method getScaleType (line 331) | public ScaleType getScaleType() {
    method onLayoutChange (line 335) | @Override
    method onTouch (line 343) | @Override
    method setAllowParentInterceptOnEdge (line 407) | public void setAllowParentInterceptOnEdge(boolean allow) {
    method setMinimumScale (line 411) | public void setMinimumScale(float minimumScale) {
    method setMediumScale (line 416) | public void setMediumScale(float mediumScale) {
    method setMaximumScale (line 421) | public void setMaximumScale(float maximumScale) {
    method setScaleLevels (line 426) | public void setScaleLevels(float minimumScale, float mediumScale, floa...
    method setOnLongClickListener (line 433) | public void setOnLongClickListener(OnLongClickListener listener) {
    method setOnClickListener (line 437) | public void setOnClickListener(View.OnClickListener listener) {
    method setOnMatrixChangeListener (line 441) | public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
    method setOnPhotoTapListener (line 445) | public void setOnPhotoTapListener(OnPhotoTapListener listener) {
    method setOnOutsidePhotoTapListener (line 449) | public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener mOu...
    method setOnViewTapListener (line 453) | public void setOnViewTapListener(OnViewTapListener listener) {
    method setOnViewDragListener (line 457) | public void setOnViewDragListener(OnViewDragListener listener) {
    method setScale (line 461) | public void setScale(float scale) {
    method setScale (line 465) | public void setScale(float scale, boolean animate) {
    method setScale (line 472) | public void setScale(float scale, float focalX, float focalY,
    method setZoomInterpolator (line 493) | public void setZoomInterpolator(Interpolator interpolator) {
    method setScaleType (line 497) | public void setScaleType(ScaleType scaleType) {
    method isZoomable (line 504) | public boolean isZoomable() {
    method setZoomable (line 508) | public void setZoomable(boolean zoomable) {
    method update (line 513) | public void update() {
    method getDisplayMatrix (line 528) | public void getDisplayMatrix(Matrix matrix) {
    method getSuppMatrix (line 535) | public void getSuppMatrix(Matrix matrix) {
    method getDrawMatrix (line 539) | private Matrix getDrawMatrix() {
    method getImageMatrix (line 545) | public Matrix getImageMatrix() {
    method setZoomTransitionDuration (line 549) | public void setZoomTransitionDuration(int milliseconds) {
    method getValue (line 560) | private float getValue(Matrix matrix, int whichValue) {
    method resetMatrix (line 568) | private void resetMatrix() {
    method setImageViewMatrix (line 575) | private void setImageViewMatrix(Matrix matrix) {
    method checkAndDisplayMatrix (line 590) | private void checkAndDisplayMatrix() {
    method getDisplayRect (line 602) | private RectF getDisplayRect(Matrix matrix) {
    method updateBaseMatrix (line 618) | private void updateBaseMatrix(Drawable drawable) {
    method checkMatrixBounds (line 682) | private boolean checkMatrixBounds() {
    method getImageViewWidth (line 740) | private int getImageViewWidth(ImageView imageView) {
    method getImageViewHeight (line 744) | private int getImageViewHeight(ImageView imageView) {
    method cancelFling (line 748) | private void cancelFling() {
    class AnimatedZoomRunnable (line 755) | private class AnimatedZoomRunnable implements Runnable {
      method AnimatedZoomRunnable (line 761) | public AnimatedZoomRunnable(final float currentZoom, final float tar...
      method run (line 770) | @Override
      method interpolate (line 785) | private float interpolate() {
    class FlingRunnable (line 793) | private class FlingRunnable implements Runnable {
      method FlingRunnable (line 798) | public FlingRunnable(Context context) {
      method cancelFling (line 802) | public void cancelFling() {
      method fling (line 806) | public void fling(int viewWidth, int viewHeight, int velocityX,
      method run (line 841) | @Override

FILE: lib-album/src/main/widget/com/sharry/lib/album/photoview/Util.java
  class Util (line 6) | class Util {
    method checkZoomLevels (line 8) | static void checkZoomLevels(float minZoom, float midZoom,
    method hasDrawable (line 19) | static boolean hasDrawable(ImageView imageView) {
    method isSupportedScaleType (line 23) | static boolean isSupportedScaleType(final ImageView.ScaleType scaleTyp...
    method getPointerIndex (line 34) | static int getPointerIndex(int action) {

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/AppBarHelper.java
  class AppBarHelper (line 19) | class AppBarHelper {
    method AppBarHelper (line 25) | private AppBarHelper(Context context) {
    method with (line 37) | static AppBarHelper with(Context context) {
    method setStatusBarStyle (line 44) | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    method setStatusBarColor (line 86) | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    method setNavigationBarStyle (line 99) | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    method setNavigationBarColor (line 140) | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    method setAllBarsHide (line 153) | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    method apply (line 168) | @TargetApi(Build.VERSION_CODES.LOLLIPOP)

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/Builder.java
  class Builder (line 33) | public class Builder {
    method Builder (line 68) | Builder(Context context) {
    method Builder (line 80) | Builder(View contentView) {
    method setSubItemInterval (line 93) | public Builder setSubItemInterval(@Dimension(unit = DP) int subItemInt...
    method setMinimumHeight (line 101) | public Builder setMinimumHeight(@Dimension(unit = DP) int minimumHeigh...
    method setStatusBarStyle (line 109) | public Builder setStatusBarStyle(Style statusBarStyle) {
    method setBackgroundColorRes (line 118) | public Builder setBackgroundColorRes(@ColorRes int colorResId) {
    method setBackgroundColor (line 126) | public Builder setBackgroundColor(@ColorInt int color) {
    method setBackgroundDrawableRes (line 135) | public Builder setBackgroundDrawableRes(@DrawableRes int drawableResId) {
    method setDividingLineHeight (line 143) | public Builder setDividingLineHeight(@Dimension(unit = DP) int height) {
    method setDividingLineColorRes (line 151) | public Builder setDividingLineColorRes(@ColorRes int dividingLineColor...
    method setDividingLineColor (line 159) | public Builder setDividingLineColor(@ColorInt int dividingLineColor) {
    method setTitleGravity (line 167) | public Builder setTitleGravity(int gravity) {
    method setTitleText (line 175) | public Builder setTitleText(CharSequence text) {
    method setTitleText (line 180) | public Builder setTitleText(CharSequence text, @Dimension(unit = SP) i...
    method setTitleText (line 185) | public Builder setTitleText(CharSequence text, @Dimension(unit = SP) i...
    method setTitleText (line 196) | public Builder setTitleText(@NonNull TextViewOptions ops) {
    method setTitleImage (line 204) | public Builder setTitleImage(@DrawableRes int drawableRes) {
    method setTitleImage (line 208) | public Builder setTitleImage(@DrawableRes int drawableRes, @Dimension(...
    method setTitleImage (line 219) | public Builder setTitleImage(@NonNull ImageViewOptions ops) {
    method addTitleView (line 227) | public Builder addTitleView(@NonNull View view) {
    method addTitleView (line 231) | public Builder addTitleView(View view, Options ops) {
    method addBackIcon (line 239) | public Builder addBackIcon(@DrawableRes int drawableRes) {
    method addLeftMenuText (line 256) | public Builder addLeftMenuText(@NonNull TextViewOptions ops) {
    method addLeftMenuImage (line 260) | public Builder addLeftMenuImage(@NonNull ImageViewOptions ops) {
    method addLeftMenuView (line 264) | public Builder addLeftMenuView(@NonNull View view) {
    method addLeftMenuView (line 268) | public Builder addLeftMenuView(@Nullable View view, @Nullable Options ...
    method addRightMenuText (line 276) | public Builder addRightMenuText(@NonNull TextViewOptions ops) {
    method addRightMenuImage (line 280) | public Builder addRightMenuImage(@NonNull ImageViewOptions ops) {
    method addRightMenuView (line 284) | public Builder addRightMenuView(@NonNull View view) {
    method addRightMenuView (line 288) | public Builder addRightMenuView(@NonNull View view, @NonNull Options o...
    method build (line 296) | public SToolbar build() {
    method apply (line 305) | public SToolbar apply() {
    method completion (line 322) | private void completion(SToolbar toolbar) {
    method adjustLayout (line 402) | private void adjustLayout(SToolbar toolbar) {
    class Entity (line 415) | private static class Entity {
      method Entity (line 420) | Entity(View view, Options op) {

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/ImageViewOptions.java
  class ImageViewOptions (line 20) | public class ImageViewOptions implements Options<ImageView> {
    method ImageViewOptions (line 49) | private ImageViewOptions() {
    method Builder (line 55) | public static Builder Builder() {
      method Builder (line 112) | private Builder() {
      method Builder (line 116) | private Builder(@NonNull ImageViewOptions other) {
      method setDrawableResId (line 121) | public Builder setDrawableResId(@DrawableRes int drawableResId) {
      method setScaleType (line 126) | public Builder setScaleType(ImageView.ScaleType scaleType) {
      method setPaddingLeft (line 131) | public Builder setPaddingLeft(@Dimension(unit = PX) int paddingLeft) {
      method setPaddingRight (line 136) | public Builder setPaddingRight(@Dimension(unit = PX) int paddingRigh...
      method setWidthWithoutPadding (line 141) | public Builder setWidthWithoutPadding(@Dimension(unit = PX) int widt...
      method setHeightWithoutPadding (line 146) | public Builder setHeightWithoutPadding(@Dimension(unit = PX) int hei...
      method setListener (line 151) | public Builder setListener(View.OnClickListener listener) {
      method build (line 156) | public ImageViewOptions build() {
    method newBuilder (line 62) | public Builder newBuilder() {
    method completion (line 66) | @Override
    method copyFrom (line 95) | private void copyFrom(@NonNull ImageViewOptions other) {
    class Builder (line 108) | public static class Builder {
      method Builder (line 112) | private Builder() {
      method Builder (line 116) | private Builder(@NonNull ImageViewOptions other) {
      method setDrawableResId (line 121) | public Builder setDrawableResId(@DrawableRes int drawableResId) {
      method setScaleType (line 126) | public Builder setScaleType(ImageView.ScaleType scaleType) {
      method setPaddingLeft (line 131) | public Builder setPaddingLeft(@Dimension(unit = PX) int paddingLeft) {
      method setPaddingRight (line 136) | public Builder setPaddingRight(@Dimension(unit = PX) int paddingRigh...
      method setWidthWithoutPadding (line 141) | public Builder setWidthWithoutPadding(@Dimension(unit = PX) int widt...
      method setHeightWithoutPadding (line 146) | public Builder setHeightWithoutPadding(@Dimension(unit = PX) int hei...
      method setListener (line 151) | public Builder setListener(View.OnClickListener listener) {
      method build (line 156) | public ImageViewOptions build() {

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/Options.java
  type Options (line 12) | public interface Options<T extends View> {
    method completion (line 17) | void completion(T view);

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/SToolbar.java
  class SToolbar (line 46) | public class SToolbar extends Toolbar {
    method SToolbar (line 82) | public SToolbar(Context context) {
    method SToolbar (line 86) | public SToolbar(Context context, AttributeSet attrs) {
    method SToolbar (line 90) | public SToolbar(Context context, @Nullable AttributeSet attrs, int def...
    method Builder (line 176) | public static Builder Builder(Context context) {
    method Builder (line 184) | public static Builder Builder(View contentView) {
    method onMeasure (line 188) | @Override
    method onDraw (line 196) | @Override
    method setLayoutParams (line 206) | @Override
    method setStatusBarStyle (line 218) | public void setStatusBarStyle(Style style) {
    method setBackgroundColorRes (line 231) | public void setBackgroundColorRes(@ColorRes int colorResId) {
    method setBackgroundDrawableRes (line 239) | public void setBackgroundDrawableRes(@DrawableRes int drawableRes) {
    method setDividingLineColorRes (line 247) | public void setDividingLineColorRes(@ColorRes int colorRes) {
    method setDividingLineColor (line 254) | public void setDividingLineColor(@ColorInt int color) {
    method setDividingLineHeight (line 261) | public void setDividingLineHeight(@Dimension(unit = DP) int dividingLi...
    method setTitleGravity (line 270) | public void setTitleGravity(int gravity) {
    method getTitleText (line 279) | public TextView getTitleText() {
    method setTitleText (line 290) | public void setTitleText(@StringRes int stringResId) {
    method setTitleText (line 294) | public void setTitleText(@NonNull CharSequence text) {
    method setTitleText (line 298) | public void setTitleText(@NonNull TextViewOptions ops) {
    method getTitleImage (line 313) | public ImageView getTitleImage() {
    method setTitleImage (line 324) | public void setTitleImage(@DrawableRes int resId) {
    method setTitleImage (line 328) | public void setTitleImage(@NonNull ImageViewOptions ops) {
    method setTitleText (line 338) | public void setTitleText(@NonNull CharSequence text, @Dimension(unit =...
    method setTitleText (line 342) | public void setTitleText(@NonNull CharSequence text, @Dimension(unit =...
    method setTitleImage (line 352) | public void setTitleImage(@DrawableRes int resId, @Dimension(unit = DP...
    method addTitleView (line 363) | public void addTitleView(@NonNull View view) {
    method addTitleView (line 371) | public void addTitleView(@NonNull View view, @Nullable Options ops) {
    method addBackIcon (line 381) | public void addBackIcon(@DrawableRes int drawableRes) {
    method addLeftMenuText (line 400) | public void addLeftMenuText(@NonNull TextViewOptions ops) {
    method addLeftMenuImage (line 412) | public void addLeftMenuImage(@NonNull ImageViewOptions ops) {
    method addLeftMenuView (line 422) | public void addLeftMenuView(@NonNull View view) {
    method addLeftMenuView (line 429) | public void addLeftMenuView(@NonNull View view, @Nullable Options ops) {
    method addRightMenuText (line 439) | public void addRightMenuText(@NonNull TextViewOptions ops) {
    method addRightMenuImage (line 451) | public void addRightMenuImage(@NonNull ImageViewOptions ops) {
    method addRightMenuView (line 461) | public void addRightMenuView(@NonNull View view) {
    method addRightMenuView (line 469) | public void addRightMenuView(@NonNull View view, @Nullable Options ops) {
    method getLeftMenuView (line 479) | public <T extends View> T getLeftMenuView(int index) {
    method getRightMenuView (line 486) | public <T extends View> T getRightMenuView(int index) {
    method addView (line 490) | @Override
    method setMinimumHeight (line 498) | @Override
    method setSubItemInterval (line 510) | void setSubItemInterval(int subItemInterval) {
    method initDefaultArgs (line 514) | private void initDefaultArgs(Context context, TypedArray array) {
    method initViews (line 526) | private void initViews(Context context) {
    method createTextView (line 562) | private TextView createTextView() {
    method createImageView (line 577) | private ImageView createImageView() {

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/Style.java
  type Style (line 10) | public enum Style {
    method Style (line 19) | Style(int val) {
    method getVal (line 23) | int getVal() {

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/TextViewOptions.java
  class TextViewOptions (line 24) | public class TextViewOptions implements Options<TextView> {
    method Builder (line 29) | public static Builder Builder() {
      method Builder (line 122) | private Builder() {
      method Builder (line 126) | private Builder(@NonNull TextViewOptions other) {
      method setText (line 131) | public Builder setText(@NonNull CharSequence text) {
      method setTextSize (line 136) | public Builder setTextSize(@Dimension(unit = SP) int textSize) {
      method setTextColor (line 141) | public Builder setTextColor(@ColorInt int textColor) {
      method setMaxEms (line 146) | public Builder setMaxEms(int maxEms) {
      method setLines (line 151) | public Builder setLines(int lines) {
      method setEllipsize (line 156) | public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
      method setPaddingLeft (line 161) | public Builder setPaddingLeft(@Dimension(unit = PX) int paddingLeft) {
      method setPaddingRight (line 166) | public Builder setPaddingRight(@Dimension(unit = PX) int paddingRigh...
      method setListener (line 171) | public Builder setListener(View.OnClickListener listener) {
      method build (line 176) | public TextViewOptions build() {
    method TextViewOptions (line 64) | private TextViewOptions() {
    method newBuilder (line 70) | public Builder newBuilder() {
    method completion (line 74) | @Override
    method copyFrom (line 103) | private void copyFrom(@NonNull TextViewOptions other) {
    class Builder (line 118) | public static class Builder {
      method Builder (line 122) | private Builder() {
      method Builder (line 126) | private Builder(@NonNull TextViewOptions other) {
      method setText (line 131) | public Builder setText(@NonNull CharSequence text) {
      method setTextSize (line 136) | public Builder setTextSize(@Dimension(unit = SP) int textSize) {
      method setTextColor (line 141) | public Builder setTextColor(@ColorInt int textColor) {
      method setMaxEms (line 146) | public Builder setMaxEms(int maxEms) {
      method setLines (line 151) | public Builder setLines(int lines) {
      method setEllipsize (line 156) | public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
      method setPaddingLeft (line 161) | public Builder setPaddingLeft(@Dimension(unit = PX) int paddingLeft) {
      method setPaddingRight (line 166) | public Builder setPaddingRight(@Dimension(unit = PX) int paddingRigh...
      method setListener (line 171) | public Builder setListener(View.OnClickListener listener) {
      method build (line 176) | public TextViewOptions build() {

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/Utils.java
  class Utils (line 15) | class Utils {
    method Utils (line 17) | Utils() {
    method isLollipop (line 26) | static boolean isLollipop() {
    method isNotEmpty (line 33) | static boolean isNotEmpty(Collection collection) {
    method isLayoutParamsSpecialValue (line 40) | static boolean isLayoutParamsSpecialValue(int paramsValue) {
    method alphaColor (line 49) | static int alphaColor(int baseColor, float alphaPercent) {
    method dp2px (line 63) | static int dp2px(Context context, float dp) {
    method px2dp (line 71) | static int px2dp(Context context, float px) {
    method getActionBarHeight (line 79) | static int getActionBarHeight(Context context) {
    method getStatusBarHeight (line 90) | static int getStatusBarHeight(Context context) {

FILE: lib-album/src/main/widget/com/sharry/lib/album/toolbar/ViewOptions.java
  class ViewOptions (line 22) | public class ViewOptions implements Options<View> {
    method ViewOptions (line 48) | private ViewOptions() {
    method newBuilder (line 51) | public Builder newBuilder() {
    method completion (line 55) | @Override
    method copyFrom (line 82) | private void copyFrom(ViewOptions other) {
    class Builder (line 100) | public static class Builder {
      method Builder (line 104) | public Builder() {
      method Builder (line 108) | private Builder(@NonNull ViewOptions other) {
      method setVisibility (line 113) | public Builder setVisibility(@Visibility int visibility) {
      method setPaddingLeft (line 118) | public Builder setPaddingLeft(@Dimension(unit = PX) int paddingLeft) {
      method setPaddingTop (line 123) | public Builder setPaddingTop(@Dimension(unit = PX) int paddingTop) {
      method setPaddingRight (line 128) | public Builder setPaddingRight(@Dimension(unit = PX) int paddingRigh...
      method setPaddingBottom (line 133) | public Builder setPaddingBottom(@Dimension(unit = PX) int paddingBot...
      method setWidthExcludePadding (line 138) | public Builder setWidthExcludePadding(@Dimension(unit = PX) int widt...
      method setHeightExcludePadding (line 143) | public Builder setHeightExcludePadding(@Dimension(unit = PX) int hei...
      method setListener (line 148) | public Builder setListener(View.OnClickListener listener) {
      method build (line 153) | public Options build() {

FILE: lib-media-recorder/src/main/api/com/sharry/lib/media/recorder/IMediaRecorder.java
  type IMediaRecorder (line 8) | public interface IMediaRecorder {
    method start (line 13) | void start();
    method pause (line 18) | void pause();
    method resume (line 23) | void resume();
    method cancel (line 28) | void cancel();
    method complete (line 33) | void complete();

FILE: lib-media-recorder/src/main/api/com/sharry/lib/media/recorder/IRecorderCallback.java
  type IRecorderCallback (line 20) | public interface IRecorderCallback {
    class Adapter (line 22) | class Adapter implements IRecorderCallback {
      method onStart (line 24) | @Override
      method onProgress (line 29) | @Override
      method onCancel (line 34) | @Override
      method onPause (line 39) | @Override
      method onResume (line 44) | @Override
      method onComplete (line 49) | @Override
      method onFailed (line 54) | @Override
    method onStart (line 90) | @MainThread
    method onProgress (line 96) | @MainThread
    method onCancel (line 99) | @MainThread
    method onPause (line 102) | @MainThread
    method onResume (line 105) | @MainThread
    method onComplete (line 108) | @MainThread
    method onFailed (line 111) | @MainThread

FILE: lib-media-recorder/src/main/api/com/sharry/lib/media/recorder/Options.java
  class Options (line 14) | public final class Options {
    class Video (line 16) | public static class Video {
      method Video (line 65) | private Video() {
      method reBuilder (line 71) | public Builder reBuilder() {
      method getVideoEncodeType (line 75) | public EncodeType.Video getVideoEncodeType() {
      method getFrameRate (line 79) | public int getFrameRate() {
      method getResolution (line 83) | public int getResolution() {
      method getRelativePath (line 87) | public String getRelativePath() {
      method getAudioOptions (line 91) | public Audio getAudioOptions() {
      method getMuxerType (line 95) | public MuxerType getMuxerType() {
      method getAuthority (line 99) | public String getAuthority() {
      class Builder (line 106) | public static class Builder {
        method Builder (line 110) | public Builder() {
        method Builder (line 114) | private Builder(Video videoOptions) {
        method setFrameRate (line 121) | public Builder setFrameRate(int frameRate) {
        method setEncodeType (line 129) | public Builder setEncodeType(@NonNull EncodeType.Video type) {
        method setMuxerType (line 137) | public Builder setMuxerType(@NonNull MuxerType muxerType) {
        method setResolution (line 145) | public Builder setResolution(@Resolution int resolution) {
        method setRelativePath (line 163) | public Builder setRelativePath(@NonNull String relativePath) {
        method setAuthority (line 168) | public Builder setAuthority(@NonNull String authority) {
        method setAudioOptions (line 176) | public Builder setAudioOptions(@NonNull Audio audioOptions) {
        method build (line 181) | public Video build() {
    class Audio (line 187) | public static class Audio {
      method Audio (line 265) | private Audio() {
      method reBuilder (line 271) | public Builder reBuilder() {
      method getAudioEncodeType (line 275) | EncodeType.Audio getAudioEncodeType() {
      method getDuration (line 279) | int getDuration() {
      method getRelativePath (line 283) | String getRelativePath() {
      method getSampleRate (line 287) | int getSampleRate() {
      method isJustEncode (line 291) | boolean isJustEncode() {
      method getChannelLayout (line 295) | int getChannelLayout() {
      method getPerSampleSize (line 299) | int getPerSampleSize() {
      method getAuthority (line 303) | public String getAuthority() {
      method getPcmProvider (line 307) | IPCMProvider getPcmProvider() {
      class Builder (line 314) | public static class Builder {
        method Builder (line 318) | public Builder() {
        method Builder (line 322) | private Builder(Audio Audio) {
        method setSampleSize (line 329) | public Builder setSampleSize(@SampleRate int sampleSize) {
        method setEncodeType (line 337) | public Builder setEncodeType(EncodeType.Audio type) {
        method setDuration (line 347) | public Builder setDuration(int duration) {
        method setRelativePath (line 365) | public Builder setRelativePath(@NonNull String relativePath) {
        method setAuthority (line 370) | public Builder setAuthority(@NonNull String authority) {
        method setIsJustEncode (line 378) | public Builder setIsJustEncode(boolean isJustEncode) {
        method setPerSampleSize (line 383) | public Builder setPerSampleSize(@PerSampleSize int perSampleSize) {
        method setChannelLayout (line 388) | public Builder setChannelLayout(@ChannelLayout int channelLayout) {
        method setPcmProvider (line 393) | public Builder setPcmProvider(@NonNull IPCMProvider pcmProvider) {
        method build (line 398) | public Audio build() {

FILE: lib-media-recorder/src/main/api/com/sharry/lib/media/recorder/SMediaRecorder.java
  class SMediaRecorder (line 24) | public final class SMediaRecorder implements IRecorderCallback {
    method with (line 31) | public static SMediaRecorder with(@NonNull Context context) {
    method SMediaRecorder (line 46) | private SMediaRecorder(Context context) {
    method onStart (line 51) | @Override
    method onProgress (line 61) | @Override
    method onPause (line 68) | @Override
    method onResume (line 75) | @Override
    method onCancel (line 82) | @Override
    method onComplete (line 91) | @Override
    method onFailed (line 100) | @Override
    method start (line 115) | @RequiresPermission(anyOf = {
    method start (line 143) | @RequiresPermission(allOf = {
    method pause (line 162) | public void pause() {
    method resume (line 171) | public void resume() {
    method cancel (line 184) | public void cancel() {
    method complete (line 198) | public void complete() {
    method addRecordCallback (line 210) | public void addRecordCallback(@NonNull IRecorderCallback callback) {
    method completionOptions (line 222) | private void completionOptions(@NonNull Options.Audio audioOptions) {
    method completionOptions (line 232) | private void completionOptions(@NonNull Options.Video videoOptions) {

FILE: lib-media-recorder/src/main/cpp/JNICall.cpp
  function EnvResult (line 66) | EnvResult JNICall::getJniEnv(JNIEnv **env) {

FILE: lib-media-recorder/src/main/cpp/JNICall.h
  type EnvResult (line 10) | typedef int EnvResult;
  function class (line 15) | class JNICall {

FILE: lib-media-recorder/src/main/cpp/OpenSLRecorder.cpp
  function recordCallback (line 33) | void recordCallback(SLAndroidSimpleBufferQueueItf caller, void *context) {

FILE: lib-media-recorder/src/main/cpp/OpenSLRecorder.h
  function class (line 15) | class OpenSLRecorder {

FILE: lib-media-recorder/src/main/cpp/RecordBuffer.h
  function class (line 11) | class RecordBuffer {

FILE: lib-media-recorder/src/main/cpp/native-bridge-recorder.cpp
  function JNIEXPORT (line 16) | JNIEXPORT jint JNICALL
  type openslesprovider (line 38) | namespace openslesprovider {
    function nativeStart (line 43) | void nativeStart(JNIEnv *, jobject jobj) {
    function nativePause (line 53) | void nativePause(JNIEnv *, jobject) {
    function nativeResume (line 59) | void nativeResume(JNIEnv *, jobject) {
    function nativeStop (line 65) | void nativeStop(JNIEnv *, jobject) {
  function registerNativeMethods (line 89) | int registerNativeMethods(JNIEnv *env, jclass cls) {

FILE: lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/AACEncoder.java
  class AACEncoder (line 22) | public class AACEncoder implements IAudioEncoder {
    method prepare (line 42) | @Override
    method encode (line 63) | @Override
    method stop (line 131) | @Override
    method calcPresentationTimeUs (line 161) | private void calcPresentationTimeUs(int size, int sampleRate, int chan...
    method writeToFile (line 168) | private void writeToFile(ByteBuffer outBuffer, MediaCodec.BufferInfo b...
    method addADTStoPacket (line 193) | private void addADTStoPacket(byte[] packet, int packetLen, int sampleR...
    method sampleRateMapperFrequency (line 229) | private int sampleRateMapperFrequency(int sampleRate) {

FILE: lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/EncodeType.java
  class EncodeType (line 10) | public class EncodeType {
    type Video (line 12) | public enum Video {
      method Video (line 18) | Video(String desc) {
      method getDesc (line 22) | public String getDesc() {
    type Audio (line 28) | public enum Audio {
      method Audio (line 41) | Audio(String mime, String suffix) {
      method getMIME (line 46) | public String getMIME() {
      method getFileSuffix (line 50) | public String getFileSuffix() {

FILE: lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/EncoderFactory.java
  class EncoderFactory (line 12) | class EncoderFactory {
    method create (line 17) | @NonNull
    method create (line 33) | @NonNull

FILE: lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/H264Encoder.java
  class H264Encoder (line 23) | public class H264Encoder implements IVideoEncoder {
    method prepare (line 47) | @Override
    method start (line 65) | @Override
    method pause (line 72) | @Override
    method resume (line 77) | @Override
    method stop (line 85) | @Override
    class RendererThread (line 112) | private final class RendererThread extends Thread {
      method RendererThread (line 121) | RendererThread() {
      method run (line 127) | @Override
      method onDestroy (line 167) | private void onDestroy() {
    class EncodeThread (line 175) | public final class EncodeThread extends Thread {
      method run (line 177) | @Override

FILE: lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/H264Render.java
  class H264Render (line 11) | public class H264Render implements ITextureRenderer {
    method H264Render (line 56) | H264Render(int textureId) {
    method onAttach (line 62) | @Override
    method onSizeChanged (line 84) | @Override
    method onDraw (line 89) | @Override
    method onDetach (line 111) | @Override
    method createBuffer (line 126) | private FloatBuffer createBuffer(float[] vertexData) {
    method createProgram (line 135) | private int createProgram(String vertexSource, String fragmentSource) {
    method compileShader (line 164) | private int compileShader(int shaderType, String source) {

FILE: lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/IAudioEncoder.java
  type IAudioEncoder (line 17) | public interface IAudioEncoder {
    method prepare (line 24) | void prepare(@NonNull Context context) throws Throwable;
    method encode (line 29) | void encode(@Nullable byte[] inputBytes) throws Throwable;
    method stop (line 34) | void stop();
    type Callback (line 39) | interface Callback {
      method onAudioFormatChanged (line 44) | void onAudioFormatChanged(MediaFormat outputFormat);
      method onAudioEncoded (line 52) | void onAudioEncoded(ByteBuffer byteBuffer, MediaCodec.BufferInfo buf...
    class Context (line 55) | class Context {
      method Context (line 63) | public Context(int sampleRate, int channelCount, int perSampleSize, ...

FILE: lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/IVideoEncoder.java
  type IVideoEncoder (line 17) | public interface IVideoEncoder {
    method prepare (line 24) | void prepare(@NonNull Context context) throws IOException;
    method start (line 26) | void start();
    method pause (line 28) | void pause();
    method resume (line 30) | void resume();
    method stop (line 32) | void stop();
    type Callback (line 34) | interface Callback {
      method onVideoFormatChanged (line 39) | void onVideoFormatChanged(MediaFormat outputFormat);
      method onVideoEncoded (line 47) | void onVideoEncoded(ByteBuffer byteBuffer, MediaCodec.BufferInfo buf...
    class Context (line 50) | class Context {
      method Context (line 58) | public Context(int frameWidth, int frameHeight, int frameRate, int t...

FILE: lib-media-recorder/src/main/muxer/com/sharry/lib/media/recorder/IMuxer.java
  type IMuxer (line 22) | public interface IMuxer {
    method prepare (line 32) | @MainThread
    method prepare (line 41) | @MainThread
    method addVideoTrack (line 47) | @MainThread
    method addAudioTrack (line 53) | @MainThread
    method execute (line 61) | @MainThread
    method stop (line 67) | void stop();
    class Parcel (line 73) | class Parcel {
      method newInstance (line 85) | static Parcel newInstance(@TrackType int trackType, ByteBuffer byteBuf,
      method Parcel (line 94) | private Parcel(@TrackType int trackType, ByteBuffer byteBuff, MediaC...

FILE: lib-media-recorder/src/main/muxer/com/sharry/lib/media/recorder/MPEG4Muxer.java
  class MPEG4Muxer (line 21) | class MPEG4Muxer implements IMuxer {
    method prepare (line 48) | @Override
    method prepare (line 57) | @Override
    method addAudioTrack (line 65) | @Override
    method addVideoTrack (line 81) | @Override
    method execute (line 97) | @Override
    method stop (line 120) | @Override
    method tryToLaunchMuxer (line 142) | private void tryToLaunchMuxer() {

FILE: lib-media-recorder/src/main/muxer/com/sharry/lib/media/recorder/MuxerFactory.java
  class MuxerFactory (line 8) | class MuxerFactory {
    method createEncoder (line 13) | static IMuxer createEncoder(MuxerType muxerType) {

FILE: lib-media-recorder/src/main/muxer/com/sharry/lib/media/recorder/MuxerType.java
  type MuxerType (line 12) | public enum MuxerType {
    method MuxerType (line 25) | MuxerType(String mime, String suffix) {
    method getMIME (line 30) | public String getMIME() {
    method getFileSuffix (line 34) | public String getFileSuffix() {

FILE: lib-media-recorder/src/main/pcmprovider/com/sharry/lib/media/recorder/DefaultPCMProvider.java
  class DefaultPCMProvider (line 14) | public class DefaultPCMProvider implements IPCMProvider, Runnable {
    method DefaultPCMProvider (line 25) | DefaultPCMProvider() {
    method start (line 42) | @Override
    method pause (line 49) | @Override
    method resume (line 54) | @Override
    method stop (line 62) | @Override
    method setOnPCMChangedListener (line 80) | @Override
    method run (line 85) | @Override

FILE: lib-media-recorder/src/main/pcmprovider/com/sharry/lib/media/recorder/IPCMProvider.java
  type IPCMProvider (line 12) | public interface IPCMProvider {
    method start (line 14) | void start();
    method pause (line 16) | void pause();
    method resume (line 18) | void resume();
    method stop (line 20) | void stop();
    method setOnPCMChangedListener (line 22) | void setOnPCMChangedListener(OnPCMChangedListener listener);
    type OnPCMChangedListener (line 24) | interface OnPCMChangedListener {
      method OnPCMChanged (line 26) | @WorkerThread

FILE: lib-media-recorder/src/main/pcmprovider/com/sharry/lib/media/recorder/OpenSLESPCMProvider.java
  class OpenSLESPCMProvider (line 10) | public class OpenSLESPCMProvider implements IPCMProvider, IPCMProvider.O...
    method start (line 18) | @Override
    method pause (line 23) | @Override
    method resume (line 28) | @Override
    method stop (line 33) | @Override
    method OnPCMChanged (line 40) | @Override
    method setOnPCMChangedListener (line 47) | @Override
    method nativeStart (line 53) | private native void nativeStart();
    method nativePause (line 55) | private native void nativePause();
    method nativeResume (line 57) | private native void nativeResume();
    method nativeStop (line 59) | private native void nativeStop();

FILE: lib-media-recorder/src/main/recorder/com/sharry/lib/media/recorder/AudioRecorder.java
  class AudioRecorder (line 24) | final class AudioRecorder extends BaseMediaRecorder implements IAudioEnc...
    method AudioRecorder (line 35) | AudioRecorder(Context context, Options.Audio options, IRecorderCallbac...
    method OnPCMChanged (line 81) | @Override
    method onAudioFormatChanged (line 93) | @Override
    method onAudioEncoded (line 101) | @Override
    method start (line 120) | @Override
    method pause (line 146) | @Override
    method resume (line 156) | @Override
    method cancel (line 166) | @Override
    method complete (line 185) | @Override
    method stop (line 206) | @Override
    method setEncodeCallback (line 215) | void setEncodeCallback(IAudioEncoder.Callback callback) {

FILE: lib-media-recorder/src/main/recorder/com/sharry/lib/media/recorder/BaseMediaRecorder.java
  class BaseMediaRecorder (line 24) | abstract class BaseMediaRecorder implements IMediaRecorder {
    method BaseMediaRecorder (line 42) | BaseMediaRecorder(Context context, final IRecorderCallback callback) {
    method performRecordFailed (line 79) | @WorkerThread
    method deleteRecordFile (line 94) | void deleteRecordFile() {
    method finalize (line 104) | @Override
    method stop (line 110) | protected abstract void stop();

FILE: lib-media-recorder/src/main/recorder/com/sharry/lib/media/recorder/VideoRecorder.java
  class VideoRecorder (line 28) | final class VideoRecorder extends BaseMediaRecorder implements IAudioEnc...
    method VideoRecorder (line 35) | VideoRecorder(Context context, Options.Video options, SCameraView came...
    method onAudioFormatChanged (line 71) | @Override
    method onAudioEncoded (line 76) | @Override
    method onVideoFormatChanged (line 92) | @Override
    method onVideoEncoded (line 97) | @Override
    method start (line 113) | @Override
    method pause (line 149) | @Override
    method resume (line 160) | @Override
    method cancel (line 171) | @Override
    method complete (line 190) | @Override
    method stop (line 214) | @Override
    method calculateRecordFrameSize (line 237) | private void calculateRecordFrameSize(int resolution, int[] frameSize,...

FILE: lib-media-recorder/src/main/utils/com/sharry/lib/media/recorder/AVPoolExecutor.java
  class AVPoolExecutor (line 24) | class AVPoolExecutor extends ThreadPoolExecutor {
    method newThread (line 43) | @Override
    method getInstance (line 53) | public static AVPoolExecutor getInstance() {
    method AVPoolExecutor (line 57) | private AVPoolExecutor(int corePoolSize, int maximumPoolSize, long kee...
    method afterExecute (line 74) | @Override

FILE: lib-media-recorder/src/main/utils/com/sharry/lib/media/recorder/FileUtil.java
  class FileUtil (line 29) | class FileUtil {
    method createAudioPendingItem (line 34) | @NonNull
    method createAudioFile (line 60) | static File createAudioFile(Context context, String relativePath, Stri...
    method createVideoPendingItem (line 84) | @NonNull
    method createVideoFile (line 110) | @NonNull
    method getUriFromFile (line 135) | static Uri getUriFromFile(Context context, String authority, File file) {
    method delete (line 143) | static void delete(Context context, Uri uri) {
    method delete (line 152) | static void delete(Context context, File file) {
    method publishPendingItem (line 163) | @TargetApi(29)
    method notifyMediaStore (line 176) | static void notifyMediaStore(Context context, String filePath) {
    method getVideoPath (line 190) | static String getVideoPath(final Context context, final Uri uri) {
    method getAudioPath (line 222) | static String getAudioPath(final Context context, final Uri uri) {

FILE: lib-media-recorder/src/main/utils/com/sharry/lib/media/recorder/NetworkUtil.java
  class NetworkUtil (line 18) | class NetworkUtil {
    method getOperatorName (line 32) | public static String getOperatorName(Context context) {
    method getNetworkState (line 49) | @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
    method isNetConnected (line 124) | @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
    method isWifiConnected (line 144) | @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)

FILE: lib-media-recorder/src/main/utils/com/sharry/lib/media/recorder/VersionUtil.java
  class VersionUtil (line 12) | class VersionUtil {
    method isJellyBeanMr1 (line 14) | static boolean isJellyBeanMr1() {
    method isLollipop (line 18) | static boolean isLollipop() {
    method isQ (line 22) | static boolean isQ() {

FILE: lib-opengles/src/main/java/com/sharry/lib/opengles/surface/ContextSharedGLSurfaceView.java
  class ContextSharedGLSurfaceView (line 14) | public class ContextSharedGLSurfaceView extends GLSurfaceView {
    method ContextSharedGLSurfaceView (line 18) | public ContextSharedGLSurfaceView(Context context) {
    method ContextSharedGLSurfaceView (line 22) | public ContextSharedGLSurfaceView(Context context, AttributeSet attrs) {
    method setEGLContext (line 45) | public void setEGLContext(EGLContext eglContext) {
    method getEGLContext (line 49) | public EGLContext getEGLContext() {

FILE: lib-opengles/src/main/java/com/sharry/lib/opengles/texture/GLTextureView.java
  class GLTextureView (line 25) | public class GLTextureView extends TextureView {
    method GLTextureView (line 30) | public GLTextureView(Context context) {
    method GLTextureView (line 34) | public GLTextureView(Context context, AttributeSet attrs) {
    method GLTextureView (line 38) | public GLTextureView(Context context, AttributeSet attrs, int defStyle...
    method setRenderer (line 82) | public void setRenderer(@Nullable ITextureRenderer renderer) {
    method requestRenderer (line 97) | public void requestRenderer() {
    method getEglContext (line 109) | public EGLContext getEglContext() {
    class RenderWorker (line 118) | static class RenderWorker extends HandlerThread implements Handler.Cal...
      method RenderWorker (line 131) | private RenderWorker(GLTextureView view) {
      method start (line 140) | @Override
      method quit (line 147) | @Override
      method quitSafely (line 152) | @Override
      method handleMessage (line 164) | @Override
      method onFrameAvailable (line 201) | @Override
      method handleSurfaceTextureChanged (line 216) | void handleSurfaceTextureChanged(SurfaceTexture surface) {
      method handleRendererChanged (line 229) | void handleRendererChanged(ITextureRenderer oldRenderer) {
      method handleSurfaceTextureSizeChanged (line 242) | void handleSurfaceTextureSizeChanged() {
      method handleDrawFrame (line 252) | void handleDrawFrame() {
      method handleDestroy (line 262) | void handleDestroy() {
      method preformSurfaceTextureChanged (line 273) | private void preformSurfaceTextureChanged(SurfaceTexture surfaceText...
      method performRendererChanged (line 291) | private void performRendererChanged(ITextureRenderer oldRenderer) {
      method performSurfaceSizeChanged (line 313) | private void performSurfaceSizeChanged() {
      method performDrawFrame (line 324) | private void performDrawFrame() {
      method performDestroy (line 339) | private void performDestroy() {

FILE: lib-opengles/src/main/java/com/sharry/lib/opengles/texture/ITextureRenderer.java
  type ITextureRenderer (line 12) | public interface ITextureRenderer {
    method onAttach (line 14) | @WorkerThread
    method onSizeChanged (line 17) | @WorkerThread
    method onDraw (line 20) | @WorkerThread
    method onDetach (line 23) | @WorkerThread

FILE: lib-opengles/src/main/java/com/sharry/lib/opengles/util/EglCore.java
  class EglCore (line 30) | public class EglCore {
    method EglCore (line 50) | public EglCore() {
    method EglCore (line 54) | public EglCore(@EGLVersion int eglVersion) {
    method initialize (line 64) | public void initialize(@NonNull Surface surface, @Nullable EGLContext ...
    method initialize (line 74) | public void initialize(@NonNull SurfaceTexture surfaceTexture, @Nullab...
    method makeCurrent (line 81) | public void makeCurrent() {
    method setPresentationTime (line 90) | public void setPresentationTime(long nsecs) {
    method swapBuffers (line 99) | public boolean swapBuffers() {
    method release (line 109) | public void release() {
    method getContext (line 127) | public EGLContext getContext() {
    method initializeInternal (line 134) | private void initializeInternal(Object nativeWindow, EGLContext shared...
    method chooseConfig (line 191) | private EGLConfig chooseConfig() {
    method finalize (line 221) | @Override

FILE: lib-opengles/src/main/java/com/sharry/lib/opengles/util/FboHelper.java
  class FboHelper (line 7) | public class FboHelper implements ITextureRenderer {
    method FboHelper (line 12) | public FboHelper() {
    method onAttach (line 15) | @Override
    method createFbo (line 21) | private void createFbo() {
    method createTexture (line 28) | private void createTexture() {
    method onSizeChanged (line 45) | @Override
    method onDraw (line 73) | @Override
    method onDetach (line 78) | @Override
    method bindFramebuffer (line 95) | public void bindFramebuffer() {
    method unbindFramebuffer (line 99) | public void unbindFramebuffer() {
    method getTexture2DId (line 103) | public int getTexture2DId() {

FILE: lib-opengles/src/main/java/com/sharry/lib/opengles/util/GlMatrixUtil.java
  class GlMatrixUtil (line 12) | public class GlMatrixUtil {
    method getMatrix (line 23) | public static void getMatrix(float[] matrix, int type, int imgWidth, i...
    method flip (line 84) | public static float[] flip(float[] m, boolean x, boolean y) {
    method scale (line 91) | public static float[] scale(float[] m, float x, float y) {
    method getOriginalMatrix (line 96) | public static float[] getOriginalMatrix() {

FILE: lib-opengles/src/main/java/com/sharry/lib/opengles/util/GlUtil.java
  class GlUtil (line 14) | public class GlUtil {
    method getGLResource (line 19) | public static String getGLResource(Context context, int rawId) {
    method createFloatBuffer (line 38) | public static FloatBuffer createFloatBuffer(float[] coords) {
    method createProgram (line 53) | public static int createProgram(String vertexSource, String fragmentSo...
    method loadShader (line 88) | private static int loadShader(int shaderType, String source) {
    method createTexture2D (line 116) | public static int createTexture2D() {
    method createOESTexture (line 137) | private int createOESTexture() {
    method bindFrameTexture (line 164) | public static void bindFrameTexture(int frameBufferId, int texture2DId) {
    method unBindFrameBuffer (line 173) | public static void unBindFrameBuffer() {
    method checkGlError (line 177) | public static void checkGlError(String operation) {

FILE: lib-opengles/src/main/utils/com/sharry/lib/opengles/EglCore.java
  class EglCore (line 30) | public class EglCore {
    method EglCore (line 50) | public EglCore() {
    method EglCore (line 54) | public EglCore(@EGLVersion int eglVersion) {
    method initialize (line 64) | public void initialize(@NonNull Surface surface, @Nullable EGLContext ...
    method initialize (line 74) | public void initialize(@NonNull SurfaceTexture surfaceTexture, @Nullab...
    method makeCurrent (line 81) | public void makeCurrent() {
    method swapBuffers (line 92) | public boolean swapBuffers() {
    method release (line 102) | public void release() {
    method getContext (line 120) | public EGLContext getContext() {
    method initializeInternal (line 127) | private void initializeInternal(Object nativeWindow, EGLContext shared...
    method chooseConfig (line 184) | private EGLConfig chooseConfig() {
    method finalize (line 214) | @Override

FILE: lib-opengles/src/main/utils/com/sharry/lib/opengles/GlUtil.java
  class GlUtil (line 13) | public class GlUtil {
    method getGLResource (line 18) | public static String getGLResource(Context context, int rawId) {
    method createFloatBuffer (line 37) | public static FloatBuffer createFloatBuffer(float[] coords) {
    method createProgram (line 52) | public  static int createProgram(String vertexSource, String fragmentS...
    method loadShader (line 87) | private static int loadShader(int shaderType, String source) {

FILE: lib-scamera/src/main/api/com/sharry/lib/camera/SCameraView.java
  class SCameraView (line 25) | public class SCameraView extends FrameLayout implements
    method SCameraView (line 98) | public SCameraView(@NonNull Context context) {
    method SCameraView (line 102) | public SCameraView(@NonNull Context context, @Nullable AttributeSet at...
    method SCameraView (line 106) | public SCameraView(@NonNull Context context, @Nullable AttributeSet at...
    method onAttachedToWindow (line 129) | @Override
    method onDetachedFromWindow (line 137) | @Override
    method onDisplayOrientationChanged (line 145) | @Override
    method onCameraReady (line 151) | @Override
    method onMeasure (line 158) | @Override
    method onLayout (line 216) | @Override
    method startPreview (line 227) | public void startPreview() {
    method stopPreview (line 240) | public void stopPreview() {
    method takePicture (line 247) | @Nullable
    method setFacing (line 259) | public void setFacing(@Facing int facing) {
    method setAspectRatio (line 269) | public void setAspectRatio(@NonNull AspectRatio ratio) {
    method setAutoFocus (line 285) | public void setAutoFocus(boolean autoFocus) {
    method setFlash (line 295) | public void setFlash(@Flash int flash) {
    method setAdjustViewBounds (line 304) | public void setAdjustViewBounds(boolean adjustViewBounds) {
    method getFacing (line 316) | public int getFacing() {
    method getAspectRatio (line 325) | @NonNull
    method getAutoFocus (line 336) | public boolean getAutoFocus() {
    method getFlash (line 345) | public int getFlash() {
    method getAdjustViewBounds (line 355) | public boolean getAdjustViewBounds() {
    method getPreviewer (line 364) | public IPreviewer getPreviewer() {
    method isLandscape (line 373) | public boolean isLandscape() {

FILE: lib-scamera/src/main/common/com/sharry/lib/camera/AspectRatio.java
  class AspectRatio (line 28) | public class AspectRatio implements Comparable<AspectRatio>, Parcelable {
    method of (line 45) | public static AspectRatio of(int x, int y) {
    method parse (line 73) | public static AspectRatio parse(String s) {
    method AspectRatio (line 87) | private AspectRatio(int x, int y) {
    method getX (line 92) | public int getX() {
    method getY (line 96) | public int getY() {
    method matches (line 100) | public boolean matches(Size size) {
    method equals (line 107) | @Override
    method toString (line 122) | @Override
    method toFloat (line 127) | public float toFloat() {
    method hashCode (line 131) | @Override
    method compareTo (line 137) | @Override
    method inverse (line 150) | public AspectRatio inverse() {
    method gcd (line 160) | private static int gcd(int a, int b) {
    method describeContents (line 169) | @Override
    method writeToParcel (line 174) | @Override
    method createFromParcel (line 183) | @Override
    method newArray (line 190) | @Override

FILE: lib-scamera/src/main/common/com/sharry/lib/camera/CameraContext.java
  class CameraContext (line 11) | class CameraContext extends ContextWrapper {
    method CameraContext (line 21) | CameraContext(Context base) {
    method getAspectRatio (line 25) | AspectRatio getAspectRatio() {
    method setAspectRatio (line 29) | void setAspectRatio(AspectRatio aspectRatio) {
    method getFacing (line 33) | int getFacing() {
    method setFacing (line 37) | void setFacing(int facing) {
    method isAutoFocus (line 41) | boolean isAutoFocus() {
    method setAutoFocus (line 45) | void setAutoFocus(boolean autoFocus) {
    method getFlashMode (line 49) | int getFlashMode() {
    method setFlashMode (line 53) | void setFlashMode(int flashMode) {
    method getScreenOrientationDegrees (line 57) | int getScreenOrientationDegrees() {
    method setScreenOrientationDegrees (line 61) | void setScreenOrientationDegrees(int screenOrientationDegrees) {
    method isAdjustViewBounds (line 65) | boolean isAdjustViewBounds() {
    method setAdjustViewBounds (line 69) | public void setAdjustViewBounds(boolean adjustViewBounds) {
    method getAdjustViewBounds (line 73) | public boolean getAdjustViewBounds() {
    method setDesiredSize (line 77) | public void setDesiredSize(Size desiredSize) {
    method getDesiredSize (line 81) | public Size getDesiredSize() {

FILE: lib-scamera/src/main/common/com/sharry/lib/camera/Constants.java
  type Constants (line 22) | interface Constants {

FILE: lib-scamera/src/main/common/com/sharry/lib/camera/Size.java
  class Size (line 24) | public class Size implements Comparable<Size> {
    method Size (line 35) | public Size(int width, int height) {
    method getWidth (line 40) | public int getWidth() {
    method getHeight (line 44) | public int getHeight() {
    method equals (line 48) | @Override
    method toString (line 63) | @Override
    method hashCode (line 68) | @Override
    method compareTo (line 74) | @Override

FILE: lib-scamera/src/main/common/com/sharry/lib/camera/SizeMap.java
  class SizeMap (line 28) | class SizeMap {
    method add (line 38) | public boolean add(Size size) {
    method remove (line 62) | public void remove(AspectRatio ratio) {
    method ratios (line 66) | Set<AspectRatio> ratios() {
    method sizes (line 70) | SortedSet<Size> sizes(AspectRatio ratio) {
    method clear (line 74) | void clear() {
    method isEmpty (line 78) | boolean isEmpty() {

FILE: lib-scamera/src/main/device/com/sharry/lib/camera/AbsCameraDevice.java
  class AbsCameraDevice (line 8) | abstract class AbsCameraDevice implements ICameraDevice {
    method AbsCameraDevice (line 19) | AbsCameraDevice(CameraContext context, OnCameraReadyListener listener) {
    method notifyFacingChanged (line 24) | @Override
    method notifyAspectRatioChanged (line 35) | @Override
    method notifyScreenOrientationChanged (line 48) | @Override
    method notifyDesiredSizeChanged (line 59) | @Override
    method isLandscape (line 77) | protected boolean isLandscape() {

FILE: lib-scamera/src/main/device/com/sharry/lib/camera/Camera1Device.java
  class Camera1Device (line 20) | class Camera1Device extends AbsCameraDevice {
    method Camera1Device (line 44) | Camera1Device(CameraContext context, OnCameraReadyListener listener) {
    method isCameraOpened (line 49) | @Override
    method open (line 54) | @Override
    method close (line 62) | @Override
    method takePicture (line 81) | @Override
    method notifyAutoFocusChanged (line 87) | @Override
    method notifyFlashModeChanged (line 105) | @Override
    method startPreviewInternal (line 126) | private void startPreviewInternal() {
    method chooseCamera (line 192) | private int chooseCamera(int facing) {
    method chooseDefaultAspectRatio (line 207) | private AspectRatio chooseDefaultAspectRatio() {
    method setAutoFocusInternal (line 223) | private void setAutoFocusInternal(boolean autoFocus) {
    method setFlashInternal (line 241) | private boolean setFlashInternal(int flash) {
    method chooseOptimalPreviewSize (line 261) | private Size chooseOptimalPreviewSize(SortedSet<Size> sizes) {
    method calcTakenPictureRotation (line 293) | private int calcTakenPictureRotation(int screenOrientationDegrees) {
    method calcPreviewFrameOrientation (line 313) | private int calcPreviewFrameOrientation(int screenOrientationDegrees) {

FILE: lib-scamera/src/main/device/com/sharry/lib/camera/ICameraDevice.java
  type ICameraDevice (line 15) | interface ICameraDevice {
    method open (line 17) | void open();
    method close (line 19) | void close();
    method takePicture (line 21) | Bitmap takePicture();
    method isCameraOpened (line 23) | boolean isCameraOpened();
    method notifyFacingChanged (line 25) | void notifyFacingChanged();
    method notifyAspectRatioChanged (line 27) | void notifyAspectRatioChanged();
    method notifyAutoFocusChanged (line 29) | void notifyAutoFocusChanged();
    method notifyFlashModeChanged (line 31) | void notifyFlashModeChanged();
    method notifyScreenOrientationChanged (line 33) | void notifyScreenOrientationChanged();
    method notifyDesiredSizeChanged (line 35) | void notifyDesiredSizeChanged();
    type OnCameraReadyListener (line 37) | interface OnCameraReadyListener {
      method onCameraReady (line 39) | void onCameraReady(@NonNull SurfaceTexture cameraTexture, @NonNull S...

FILE: lib-scamera/src/main/orientation/com/sharry/lib/camera/ScreenOrientationDetector.java
  class ScreenOrientationDetector (line 16) | class ScreenOrientationDetector {
    method ScreenOrientationDetector (line 38) | ScreenOrientationDetector(Context context, final OnDisplayChangedListe...
    method enable (line 56) | void enable(Display display) {
    method disable (line 64) | void disable() {
    method isLandscape (line 69) | boolean isLandscape() {
    type OnDisplayChangedListener (line 75) | interface OnDisplayChangedListener {
      method onDisplayOrientationChanged (line 82) | void onDisplayOrientationChanged(int displayOrientation);

FILE: lib-scamera/src/main/previewer/com/sharry/lib/camera/DefaultPreviewerRenderer.java
  class DefaultPreviewerRenderer (line 17) | public class DefaultPreviewerRenderer extends PreviewerRendererWrapper {
    method DefaultPreviewerRenderer (line 56) | public DefaultPreviewerRenderer(Context context) {
    method onAttach (line 60) | @Override
    method setupShaders (line 69) | private void setupShaders() {
    method setupCoordinates (line 76) | private void setupCoordinates() {
    method onDrawTexture (line 108) | @Override
    method onDetach (line 132) | @Override

FILE: lib-scamera/src/main/previewer/com/sharry/lib/camera/IPreviewer.java
  type IPreviewer (line 20) | public interface IPreviewer {
    method setDataSource (line 25) | void setDataSource(@NonNull SurfaceTexture bufferTexture);
    method setRenderer (line 30) | void setRenderer(@Nullable Renderer renderer);
    method setRotate (line 35) | void setRotate(int degrees);
    method setScaleType (line 40) | void setScaleType(ScaleType type, boolean landscape, Size dataSourceSi...
    method getRenderer (line 45) | @NonNull
    method getView (line 51) | View getView();
    method getSize (line 56) | @NonNull
    method getBitmap (line 62) | Bitmap getBitmap();
    method getEglContext (line 67) | EGLContext getEglContext();
    type Renderer (line 78) | interface Renderer extends ITextureRenderer {
      method setDataSource (line 85) | void setDataSource(SurfaceTexture dataSource);
      method getPreviewerTextureId (line 90) | int getPreviewerTextureId();
      method setRotate (line 95) | void setRotate(int degrees);
      method setScaleType (line 100) | void setScaleType(ScaleType type, boolean landscape, Size dataSource...

FILE: lib-scamera/src/main/previewer/com/sharry/lib/camera/Previewer.java
  class Previewer (line 29) | @SuppressLint("ViewConstructor")
    method Previewer (line 37) | Previewer(Context context, FrameLayout parent) {
    method setDataSource (line 58) | @Override
    method setRenderer (line 75) | @Override
    method setRotate (line 89) | @Override
    method setScaleType (line 98) | @Override
    method getView (line 109) | @Override
    method getRenderer (line 114) | @NonNull
    method getSize (line 120) | @Override
    method getBitmap (line 125) | @Override
    class FrameAvailableListenerImpl (line 132) | private static class FrameAvailableListenerImpl extends WeakReference<...
      method FrameAvailableListenerImpl (line 135) | private FrameAvailableListenerImpl(Previewer referent) {
      method onFrameAvailable (line 139) | @Override

FILE: lib-scamera/src/main/previewer/com/sharry/lib/camera/PreviewerRendererImpl.java
  class PreviewerRendererImpl (line 25) | public class PreviewerRendererImpl implements IPreviewer.Renderer {
    method PreviewerRendererImpl (line 81) | public PreviewerRendererImpl(Context context) {
    method onAttach (line 94) | @Override
    method setupShaders (line 105) | private void setupShaders() {
    method setupCoordinates (line 118) | private void setupCoordinates() {
    method createOESTexture (line 150) | private int createOESTexture() {
    method onSizeChanged (line 167) | @Override
    method onDraw (line 177) | @Override
    method attachDataSource (line 205) | private void attachDataSource() {
    method draw (line 219) | private void draw() {
    method onDetach (line 258) | @Override
    method setDataSource (line 286) | @Override
    method setRotate (line 294) | @Override
    method setScaleType (line 299) | @Override
    method getPreviewerTextureId (line 331) | @Override

FILE: lib-scamera/src/main/previewer/com/sharry/lib/camera/PreviewerRendererWrapper.java
  class PreviewerRendererWrapper (line 10) | public abstract class PreviewerRendererWrapper implements IPreviewer.Ren...
    method PreviewerRendererWrapper (line 14) | public PreviewerRendererWrapper(IPreviewer.Renderer impl) {
    method onAttach (line 18) | @Override
    method onSizeChanged (line 23) | @Override
    method onDraw (line 28) | @Override
    method onDetach (line 34) | @Override
    method setDataSource (line 39) | @Override
    method getPreviewerTextureId (line 44) | @Override
    method setRotate (line 49) | @Override
    method setScaleType (line 54) | @Override
    method onDrawTexture (line 59) | protected abstract void onDrawTexture(int textureId);

FILE: lib-scamera/src/main/previewer/com/sharry/lib/camera/ScaleType.java
  type ScaleType (line 3) | public enum ScaleType {
Condensed preview — 228 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (795K chars).
[
  {
    "path": ".gitignore",
    "chars": 412,
    "preview": "# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n\n# Gradle files\n.gradl"
  },
  {
    "path": "README.md",
    "chars": 9742,
    "preview": "## SAlbum\nSAlbum 是一款对 Android 端提供 **图片的选取、裁剪、拍摄和短视频录制等功能的图库框架**\n\n## 功能介绍\n- **图片的选取**\n  - 支持 JPEG/PNG/WEBP/GIF 的选取\n  - 图片"
  },
  {
    "path": "app/.gitignore",
    "chars": 471,
    "preview": "/build\n# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generat"
  },
  {
    "path": "app/build.gradle",
    "chars": 1451,
    "preview": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-android-extensions'\napply p"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 916,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in D:"
  },
  {
    "path": "app/release/output.json",
    "chars": 215,
    "preview": "[{\"outputType\":{\"type\":\"APK\"},\"apkInfo\":{\"type\":\"MAIN\",\"splits\":[],\"versionCode\":-1,\"enabled\":true,\"outputFile\":\"app-rel"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1482,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:to"
  },
  {
    "path": "app/src/main/java/com/sharry/app/salbum/MainActivity.kt",
    "chars": 5921,
    "preview": "package com.sharry.app.salbum\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.text.TextUtils\nimp"
  },
  {
    "path": "app/src/main/java/com/sharry/app/salbum/WatermarkPreviewerRenderer.java",
    "chars": 12587,
    "preview": "package com.sharry.app.salbum;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics."
  },
  {
    "path": "app/src/main/res/drawable/app_activity_main_launcher.xml",
    "chars": 202,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <corners "
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
    "chars": 1720,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"108dp\"\n        android:height="
  },
  {
    "path": "app/src/main/res/layout/app_activity_main.xml",
    "chars": 4149,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 267,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 267,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 208,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimaryDark\">#00b0ff</color>\n    <color name=\"c"
  },
  {
    "path": "app/src/main/res/values/ic_launcher_background.xml",
    "chars": 120,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#3576BB</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 668,
    "preview": "<resources>\n    <string name=\"app_name\" translatable=\"false\">SAlbum</string>\n\n    <string name=\"app_activity_main_et_thr"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 381,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">"
  },
  {
    "path": "app/src/main/res/values-zh/strings.xml",
    "chars": 566,
    "preview": "<resources>\n\n    <string name=\"app_activity_main_et_threshold_hint\">选中数量</string>\n    <string name=\"app_activity_main_et"
  },
  {
    "path": "app/src/main/res/xml/provider_paths.xml",
    "chars": 250,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <external-path\n        name=\"external_files\"\n        path=\".\" />\n    "
  },
  {
    "path": "build.gradle",
    "chars": 1086,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    r"
  },
  {
    "path": "git",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 231,
    "preview": "#Sun Apr 28 15:08:24 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": 2314,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "lib-album/.gitignore",
    "chars": 471,
    "preview": "/build\n# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generat"
  },
  {
    "path": "lib-album/build.gradle",
    "chars": 1732,
    "preview": "apply plugin: 'com.android.library'\napply plugin: 'com.github.dcendents.android-maven'\n\ngroup = 'com.github.SharryChoo'\n"
  },
  {
    "path": "lib-album/proguard-rules.pro",
    "chars": 751,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "lib-album/src/main/AndroidManifest.xml",
    "chars": 1203,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "lib-album/src/main/base/com/sharry/lib/album/ILoaderEngine.java",
    "chars": 741,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.widget.ImageView;\n\nimport androidx.annotat"
  },
  {
    "path": "lib-album/src/main/base/com/sharry/lib/album/Loader.java",
    "chars": 1704,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.util.Log;\nimport android.widget.ImageView;"
  },
  {
    "path": "lib-album/src/main/base/com/sharry/lib/album/MediaMeta.java",
    "chars": 4142,
    "preview": "package com.sharry.lib.album;\n\nimport android.net.Uri;\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport a"
  },
  {
    "path": "lib-album/src/main/copper/com/sharry/lib/album/CropperCallback.java",
    "chars": 348,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.NonNull;\n\n/**\n * 图片裁剪的回调\n *\n * @author Sharry <a href=\"sharryc"
  },
  {
    "path": "lib-album/src/main/copper/com/sharry/lib/album/CropperCallbackLambda.java",
    "chars": 294,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.Nullable;\n\n/**\n * 图片裁剪的回调\n *\n * @author Sharry <a href=\"sharry"
  },
  {
    "path": "lib-album/src/main/copper/com/sharry/lib/album/CropperConfig.java",
    "chars": 5487,
    "preview": "package com.sharry.lib.album;\n\nimport android.net.Uri;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport an"
  },
  {
    "path": "lib-album/src/main/copper/com/sharry/lib/album/CropperFragment.java",
    "chars": 7224,
    "preview": "package com.sharry.lib.album;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.app.FragmentMana"
  },
  {
    "path": "lib-album/src/main/copper/com/sharry/lib/album/CropperManager.java",
    "chars": 3234,
    "preview": "package com.sharry.lib.album;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Context;\nimp"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/FolderAdapter.java",
    "chars": 2796,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.V"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/FolderModel.java",
    "chars": 876,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerActivity.java",
    "chars": 15374,
    "preview": "package com.sharry.lib.album;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.content.Broadcas"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerAdapter.java",
    "chars": 15767,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.os.Handler;\nimport"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerCallback.java",
    "chars": 474,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\n\n/**\n * 图片选择器的回调\n *\n * @"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerCallbackLambda.java",
    "chars": 414,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\n\n/**\n * 图片选择器的回调\n *\n * "
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerConfig.java",
    "chars": 10836,
    "preview": "package com.sharry.lib.album;\n\nimport android.graphics.Color;\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\ni"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerContract.java",
    "chars": 2616,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.view.View;\n\nimport androidx.annotation.Dra"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerManager.java",
    "chars": 4310,
    "preview": "package com.sharry.lib.album;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Context;\nimp"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerModel.java",
    "chars": 20863,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\ni"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/PickerPresenter.java",
    "chars": 12383,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.Looper;\nimpo"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_bottom_indicator.xml",
    "chars": 383,
    "preview": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    "
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_camera_header.xml",
    "chars": 1297,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"50dp\"\n    android:height=\"50dp\"\n  "
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_fab.xml",
    "chars": 352,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_gif.xml",
    "chars": 904,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_right_arrow.xml",
    "chars": 335,
    "preview": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    "
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_video_default.xml",
    "chars": 117,
    "preview": "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <solid android:color=\"#ff000000\" />\n\n</shape>"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/drawable/ic_album_picker_video_play.xml",
    "chars": 289,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"40dp\"\n    android:height=\"40dp\"\n  "
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_activity_picker.xml",
    "chars": 4573,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schema"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_recycle_item_folder.xml",
    "chars": 856,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_recycle_item_header_camera.xml",
    "chars": 504,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:a"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_recycle_item_picture.xml",
    "chars": 1092,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/layout/lib_album_recycle_item_video.xml",
    "chars": 1542,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/values/picker_colors.xml",
    "chars": 812,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <color name=\"lib_album_picker_theme_primary_color\">#ff00b0ff</co"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/values/picker_strings.xml",
    "chars": 926,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"lib_album_picker_ensure\">Ensure</string>\n    <stri"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/values/picker_themes.xml",
    "chars": 420,
    "preview": "<resources>\n\n    <style name=\"PickerTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <!-- Customize your theme"
  },
  {
    "path": "lib-album/src/main/picker/com/sharry/lib/album/res/values-zh/picker_strings.xml",
    "chars": 763,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--PickerActivity 需要用到的字符串-->\n    <string name=\"lib_album_picke"
  },
  {
    "path": "lib-album/src/main/player/com/sharry/lib/album/VideoPlayerActivity.java",
    "chars": 9863,
    "preview": "package com.sharry.lib.album;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimpo"
  },
  {
    "path": "lib-album/src/main/player/com/sharry/lib/album/res/drawable/ic_album_player_video_pasue.xml",
    "chars": 341,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"40dp\"\n    android:height=\"40dp\"\n  "
  },
  {
    "path": "lib-album/src/main/player/com/sharry/lib/album/res/drawable/ic_album_player_video_play.xml",
    "chars": 289,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"40dp\"\n    android:height=\"40dp\"\n  "
  },
  {
    "path": "lib-album/src/main/player/com/sharry/lib/album/res/layout/lib_album_activity_video_player.xml",
    "chars": 2852,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "lib-album/src/main/player/com/sharry/lib/album/res/layout-land/lib_album_activity_video_player.xml",
    "chars": 2852,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "lib-album/src/main/player/com/sharry/lib/album/res/values/player_color.xml",
    "chars": 127,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <color name=\"lib_album_player_bg_color\">#FF000000</color>\n\n</res"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/AspectRatioFragment.java",
    "chars": 4693,
    "preview": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/ITakerContract.java",
    "chars": 1971,
    "preview": "package com.sharry.lib.album;\n\nimport android.graphics.Bitmap;\nimport android.net.Uri;\n\nimport androidx.annotation.IntDe"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/TakerActivity.java",
    "chars": 13167,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Bi"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/TakerCallback.java",
    "chars": 419,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.NonNull;\n\n/**\n * 拍照回调\n *\n * @author Sharry <a href=\"sharrychoo"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/TakerCallbackLambda.java",
    "chars": 390,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.Nullable;\n\n/**\n * 拍照回调\n *\n * @author Sharry <a href=\"sharrycho"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/TakerConfig.java",
    "chars": 9536,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.os.Parcel;\n"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/TakerManager.java",
    "chars": 4519,
    "preview": "package com.sharry.lib.album;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Context;\nimp"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/TakerPresenter.java",
    "chars": 8881,
    "preview": "package com.sharry.lib.album;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.gr"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/drawable/ic_album_taker_aspect.xml",
    "chars": 477,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/drawable/ic_album_taker_camera_switch.xml",
    "chars": 487,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/drawable/ic_album_taker_denied.xml",
    "chars": 1299,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"256dp\"\n    android:height=\"256dp\"\n"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/drawable/ic_album_taker_full_screen.xml",
    "chars": 397,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/drawable/ic_album_taker_granted.xml",
    "chars": 919,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"256dp\"\n    android:height=\"256dp\"\n"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/layout/lib_ablum_activity_taker.xml",
    "chars": 3520,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/values/taker_colors.xml",
    "chars": 139,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <color name=\"lib_album_taker_ensure_panel_bg_color\">#5E000000</c"
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/values/taker_strings.xml",
    "chars": 493,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"lib_album_taker_take_picture_failed\">Take picture "
  },
  {
    "path": "lib-album/src/main/taker/com/sharry/lib/album/res/values-zh/taker_strings.xml",
    "chars": 421,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"lib_album_taker_take_picture_failed\">获取照片失败</strin"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/ActivityStateUtil.java",
    "chars": 1224,
    "preview": "package com.sharry.lib.album;\n\nimport android.app.Activity;\nimport android.content.pm.ActivityInfo;\nimport android.os.Bu"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/CallbackFragment.java",
    "chars": 2522,
    "preview": "package com.sharry.lib.album;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.app.FragmentMana"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/ColorUtil.java",
    "chars": 2824,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.FloatRange;\n\n/**\n * 处理颜色相"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/CompressUtil.java",
    "chars": 5156,
    "preview": "package com.sharry.lib.album;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.gra"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/Constants.java",
    "chars": 764,
    "preview": "package com.sharry.lib.album;\n\n/**\n * @author Sharry <a href=\"sharrychoochn@gmail.com\">Contact me.</a>\n * @version 1.0\n "
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/DateUtil.java",
    "chars": 819,
    "preview": "package com.sharry.lib.album;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Locale;\nimport java.util.TimeZone;\n\n/"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/DensityUtil.java",
    "chars": 479,
    "preview": "package com.sharry.lib.album;\n\nimport android.content.Context;\nimport android.util.TypedValue;\n\n/**\n * @author Sharry <a"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/FileUtil.java",
    "chars": 8548,
    "preview": "package com.sharry.lib.album;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentResolver;\nimport andro"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/PermissionsCallback.java",
    "chars": 234,
    "preview": "package com.sharry.lib.album;\n\n/**\n * 权限请求的回调\n *\n * @author Sharry <a href=\"SharryChooCHN@Gmail.com\">Contact me.</a>\n * "
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/PermissionsFragment.java",
    "chars": 4433,
    "preview": "package com.sharry.lib.album;\n\nimport android.annotation.TargetApi;\nimport android.app.Fragment;\nimport android.content."
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/PermissionsHelper.java",
    "chars": 4795,
    "preview": "package com.sharry.lib.album;\n\nimport android.app.Activity;\nimport android.app.FragmentManager;\nimport android.content.C"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/Preconditions.java",
    "chars": 1408,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.t"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/SharedElementHelper.java",
    "chars": 12133,
    "preview": "package com.sharry.lib.album;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorSet;\nimport android.a"
  },
  {
    "path": "lib-album/src/main/utils/com/sharry/lib/album/VersionUtil.java",
    "chars": 544,
    "preview": "package com.sharry.lib.album;\n\nimport android.os.Build;\n\n/**\n * 版本控制相关的工具类\n *\n * @author Sharry <a href=\"SharryChooCHN@G"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/DisplayAdapter.java",
    "chars": 1086,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.NonNull;\nimport androidx.fragment.app.FragmentManager;\nimport "
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/PickedPanelAdapter.java",
    "chars": 2140,
    "preview": "package com.sharry.lib.album;\n\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/WatcherActivity.java",
    "chars": 14178,
    "preview": "package com.sharry.lib.album;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimpo"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/WatcherCallback.java",
    "chars": 764,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\n\n/**\n * Created by Sharr"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/WatcherCallbackLambda.java",
    "chars": 361,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\n\n/**\n * Watcher Picker "
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/WatcherConfig.java",
    "chars": 4571,
    "preview": "package com.sharry.lib.album;\n\nimport android.graphics.Color;\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\ni"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/WatcherContract.java",
    "chars": 2019,
    "preview": "package com.sharry.lib.album;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StringRes;\n\nimport java.ut"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/WatcherFragment.java",
    "chars": 3819,
    "preview": "package com.sharry.lib.album;\n\nimport android.os.Bundle;\nimport android.util.SparseArray;\nimport android.view.LayoutInfl"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/WatcherManager.java",
    "chars": 4224,
    "preview": "package com.sharry.lib.album;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Context;\nimp"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/WatcherPresenter.java",
    "chars": 6703,
    "preview": "package com.sharry.lib.album;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport java.text.MessageFormat;\nimp"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/res/drawable/ic_album_watcher_right_arrow.xml",
    "chars": 335,
    "preview": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    "
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/res/drawable/ic_album_watcher_video_play.xml",
    "chars": 289,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"40dp\"\n    android:height=\"40dp\"\n  "
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/res/layout/lib_album_activity_watcher.xml",
    "chars": 2098,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\""
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/res/layout/lib_album_fragment_watcher_pager.xml",
    "chars": 673,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/res/values/watcher_colors.xml",
    "chars": 312,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--PictureWatcher-->\n    <color name=\"lib_album_watcher_bg_colo"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/res/values/watcher_strings.xml",
    "chars": 545,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--WatcherActivity 需要用到的字符串-->\n    <string name=\"lib_album_watc"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/res/values/watcher_themes.xml",
    "chars": 311,
    "preview": "<resources>\n\n    <style name=\"WatcherTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"android:wind"
  },
  {
    "path": "lib-album/src/main/watcher/com/sharry/lib/album/res/values-zh/watcher_strings.xml",
    "chars": 545,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--WatcherActivity 需要用到的字符串-->\n    <string name=\"lib_album_watc"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/CheckedIndicatorView.java",
    "chars": 6687,
    "preview": "package com.sharry.lib.album;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimpo"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/DraggableViewPager.java",
    "chars": 13418,
    "preview": "package com.sharry.lib.album;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimpo"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/PicturePickerFabBehavior.java",
    "chars": 5754,
    "preview": "package com.sharry.lib.album;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimpo"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/RecorderButton.java",
    "chars": 11711,
    "preview": "package com.sharry.lib.album;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimpo"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/Compat.java",
    "chars": 1435,
    "preview": "/*******************************************************************************\n * Copyright 2011, 2012 Chris Banes.\n *"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/CustomGestureDetector.java",
    "chars": 7445,
    "preview": "/*******************************************************************************\n * Copyright 2011, 2012 Chris Banes.\n *"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/OnGestureListener.java",
    "chars": 1029,
    "preview": "/*******************************************************************************\n * Copyright 2011, 2012 Chris Banes.\n *"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/OnMatrixChangedListener.java",
    "chars": 523,
    "preview": "package com.sharry.lib.album.photoview;\n\nimport android.graphics.RectF;\n\n/**\n * Interface definition for a callback to b"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/OnOutsidePhotoTapListener.java",
    "chars": 298,
    "preview": "package com.sharry.lib.album.photoview;\n\nimport android.widget.ImageView;\n\n/**\n * Callback when the user tapped outside "
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/OnPhotoTapListener.java",
    "chars": 754,
    "preview": "package com.sharry.lib.album.photoview;\n\nimport android.widget.ImageView;\n\n/**\n * A callback to be invoked when the Phot"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/OnScaleChangedListener.java",
    "chars": 517,
    "preview": "package com.sharry.lib.album.photoview;\n\n\n/**\n * Interface definition for callback to be invoked when attached ImageView"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/OnSingleFlingListener.java",
    "chars": 694,
    "preview": "package com.sharry.lib.album.photoview;\n\nimport android.view.MotionEvent;\n\n/**\n * A callback to be invoked when the Imag"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/OnViewDragListener.java",
    "chars": 500,
    "preview": "package com.sharry.lib.album.photoview;\n\n/**\n * Interface definition for a callback to be invoked when the photo is expe"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/OnViewTapListener.java",
    "chars": 546,
    "preview": "package com.sharry.lib.album.photoview;\n\nimport android.view.View;\n\npublic interface OnViewTapListener {\n\n    /**\n     *"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/PhotoView.java",
    "chars": 8063,
    "preview": "/*******************************************************************************\n * Copyright 2011, 2012 Chris Banes.\n *"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/PhotoViewAttacher.java",
    "chars": 29523,
    "preview": "/*******************************************************************************\n * Copyright 2011, 2012 Chris Banes.\n *"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/photoview/Util.java",
    "chars": 1261,
    "preview": "package com.sharry.lib.album.photoview;\n\nimport android.view.MotionEvent;\nimport android.widget.ImageView;\n\nclass Util {"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/AppBarHelper.java",
    "chars": 5720,
    "preview": "package com.sharry.lib.album.toolbar;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android."
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/Builder.java",
    "chars": 14502,
    "preview": "package com.sharry.lib.album.toolbar;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graph"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/ImageViewOptions.java",
    "chars": 4962,
    "preview": "package com.sharry.lib.album.toolbar;\n\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Im"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/Options.java",
    "chars": 371,
    "preview": "package com.sharry.lib.album.toolbar;\n\nimport android.view.View;\n\n/**\n * Options associated with <T extends View>\n *\n * "
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/SToolbar.java",
    "chars": 21616,
    "preview": "package com.sharry.lib.album.toolbar;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.conte"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/Style.java",
    "chars": 397,
    "preview": "package com.sharry.lib.album.toolbar;\n\n/**\n * StatusBar/NavigationBar 的样式\n *\n * @author Sharry <a href=\"SharryChooCHN@Gm"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/TextViewOptions.java",
    "chars": 5238,
    "preview": "package com.sharry.lib.album.toolbar;\n\nimport android.graphics.Color;\nimport android.text.TextUtils;\nimport android.util"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/Utils.java",
    "chars": 2836,
    "preview": "package com.sharry.lib.album.toolbar;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.util.Type"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/ViewOptions.java",
    "chars": 4876,
    "preview": "package com.sharry.lib.album.toolbar;\n\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotat"
  },
  {
    "path": "lib-album/src/main/widget/com/sharry/lib/album/toolbar/res/values/lib_toolbar_attrs.xml",
    "chars": 1475,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <declare-styleable name=\"SToolbar\">\n        <!--Toolbar-->\n     "
  },
  {
    "path": "lib-media-recorder/.gitignore",
    "chars": 491,
    "preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated file"
  },
  {
    "path": "lib-media-recorder/CMakeLists.txt",
    "chars": 394,
    "preview": "# CMake 最小编译版本\nCMAKE_MINIMUM_REQUIRED(VERSION 3.4.1)\n\n# 添加要打包的资源\nFILE(GLOB SRC_LISTS \"${PROJECT_SOURCE_DIR}/src/main/cpp"
  },
  {
    "path": "lib-media-recorder/Readme.markdown",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "lib-media-recorder/build.gradle",
    "chars": 1028,
    "preview": "apply plugin: 'com.android.library'\napply plugin: 'com.github.dcendents.android-maven'\n\ngroup = 'com.github.SharryChoo'\n"
  },
  {
    "path": "lib-media-recorder/proguard-rules.pro",
    "chars": 751,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "lib-media-recorder/src/main/AndroidManifest.xml",
    "chars": 410,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.sharry.lib.media.recorder\">\n\n    <"
  },
  {
    "path": "lib-media-recorder/src/main/api/com/sharry/lib/media/recorder/IMediaRecorder.java",
    "chars": 443,
    "preview": "package com.sharry.lib.media.recorder;\n\n/**\n * @author Sharry <a href=\"xiaoyu.zhu@1hai.cn\">Contact me.</a>\n * @version 1"
  },
  {
    "path": "lib-media-recorder/src/main/api/com/sharry/lib/media/recorder/IRecorderCallback.java",
    "chars": 2336,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.net.Uri;\n\nimport androidx.annotation.IntDef;\nimport androidx.anno"
  },
  {
    "path": "lib-media-recorder/src/main/api/com/sharry/lib/media/recorder/Options.java",
    "chars": 10160,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport androidx.annotation.IntDef;\nimport androidx.annotation.NonNull;\n\nimport j"
  },
  {
    "path": "lib-media-recorder/src/main/api/com/sharry/lib/media/recorder/SMediaRecorder.java",
    "chars": 6516,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.Manifest;\nimport android.content.Context;\nimport android.media.Au"
  },
  {
    "path": "lib-media-recorder/src/main/cpp/ConstDefine.h",
    "chars": 613,
    "preview": "//\n// Created by Sharry Choo on 2019-06-17.\n//\n#ifndef SMEDIA_RECORDER_CONSTDEFINE_H\n#define SMEDIA_RECORDER_CONSTDEFINE"
  },
  {
    "path": "lib-media-recorder/src/main/cpp/JNICall.cpp",
    "chars": 2553,
    "preview": "//\n// Created by Sharry Choo on 2019-06-22.\n//\n\n#include \"JNICall.h\"\n#include \"ConstDefine.h\"\n\n/////////////////////////"
  },
  {
    "path": "lib-media-recorder/src/main/cpp/JNICall.h",
    "chars": 822,
    "preview": "//\n// Created by Sharry Choo on 2019-06-22.\n//\n\n#ifndef SMEDIA_RECORDER_JNICALL_H\n#define SMEDIA_RECORDER_JNICALL_H\n\n#in"
  },
  {
    "path": "lib-media-recorder/src/main/cpp/OpenSLRecorder.cpp",
    "chars": 4441,
    "preview": "//\n// Created by Sharry on 2019-08-26.\n//\n\n#include <cassert>\n#include <pthread.h>\n#include \"OpenSLRecorder.h\"\n#include "
  },
  {
    "path": "lib-media-recorder/src/main/cpp/OpenSLRecorder.h",
    "chars": 851,
    "preview": "//\n// Created by Sharry on 2019-08-26.\n//\n\n#ifndef SMEDIA_OPENSLRECORDER_H\n#define SMEDIA_OPENSLRECORDER_H\n\n#include <SL"
  },
  {
    "path": "lib-media-recorder/src/main/cpp/RecordBuffer.cpp",
    "chars": 554,
    "preview": "//\n// Created by Sharry Choo on 2019-08-26.\n//\n\n#include \"RecordBuffer.h\"\n\nRecordBuffer::RecordBuffer(int buffer_size) {"
  },
  {
    "path": "lib-media-recorder/src/main/cpp/RecordBuffer.h",
    "chars": 370,
    "preview": "//\n// Created by Sharry Choo on 2019-08-26.\n//\n\n#ifndef SMEDIA_RECORDBUFFER_H\n#define SMEDIA_RECORDBUFFER_H\n\n\n#include <"
  },
  {
    "path": "lib-media-recorder/src/main/cpp/native-bridge-recorder.cpp",
    "chars": 2576,
    "preview": "//\n// Created by Sharry on 2019-08-26.\n//\n\n\n#include <jni.h>\n#include \"ConstDefine.h\"\n#include \"OpenSLRecorder.h\"\n#inclu"
  },
  {
    "path": "lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/AACEncoder.java",
    "chars": 8303,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.media.MediaCodec;\nimport android.media.MediaCodecInfo;\nimport and"
  },
  {
    "path": "lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/EncodeType.java",
    "chars": 974,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.media.MediaFormat;\n\n/**\n * @author Sharry <a href=\"xiaoyu.zhu@1ha"
  },
  {
    "path": "lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/EncoderFactory.java",
    "chars": 984,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport androidx.annotation.NonNull;\n\n/**\n * 编码器 的 简单工厂\n *\n * @author Sharry <a h"
  },
  {
    "path": "lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/H264Encoder.java",
    "chars": 8101,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.media.MediaCodec;\nimport android.media.MediaCodecInfo;\nimport and"
  },
  {
    "path": "lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/H264Render.java",
    "chars": 6048,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.opengl.GLES20;\n\nimport com.sharry.lib.opengles.texture.ITextureRe"
  },
  {
    "path": "lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/IAudioEncoder.java",
    "chars": 1961,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.media.MediaCodec;\nimport android.media.MediaFormat;\n\nimport andro"
  },
  {
    "path": "lib-media-recorder/src/main/encoder/com/sharry/lib/media/recorder/IVideoEncoder.java",
    "chars": 1847,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.media.MediaCodec;\nimport android.media.MediaFormat;\nimport androi"
  },
  {
    "path": "lib-media-recorder/src/main/muxer/com/sharry/lib/media/recorder/IMuxer.java",
    "chars": 2247,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport andr"
  },
  {
    "path": "lib-media-recorder/src/main/muxer/com/sharry/lib/media/recorder/MPEG4Muxer.java",
    "chars": 4402,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport andr"
  },
  {
    "path": "lib-media-recorder/src/main/muxer/com/sharry/lib/media/recorder/MuxerFactory.java",
    "chars": 527,
    "preview": "package com.sharry.lib.media.recorder;\n\n/**\n * @author Sharry <a href=\"xiaoyu.zhu@1hai.cn\">Contact me.</a>\n * @version 1"
  },
  {
    "path": "lib-media-recorder/src/main/muxer/com/sharry/lib/media/recorder/MuxerType.java",
    "chars": 644,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.media.MediaMuxer;\n\n/**\n * 混音器(描述视频的封装格式)\n *\n * @author Sharry <a "
  },
  {
    "path": "lib-media-recorder/src/main/pcmprovider/com/sharry/lib/media/recorder/DefaultPCMProvider.java",
    "chars": 2817,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.media.AudioFormat;\nimport android.media.AudioRecord;\nimport andro"
  },
  {
    "path": "lib-media-recorder/src/main/pcmprovider/com/sharry/lib/media/recorder/IPCMProvider.java",
    "chars": 505,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport androidx.annotation.WorkerThread;\n\n/**\n * 音频 PCM 数据源的提供者\n *\n * @author Sh"
  },
  {
    "path": "lib-media-recorder/src/main/pcmprovider/com/sharry/lib/media/recorder/OpenSLESPCMProvider.java",
    "chars": 1178,
    "preview": "package com.sharry.lib.media.recorder;\n\n/**\n * 使用 OpenSL ES 实现的音频录制引擎\n *\n * @author Sharry <a href=\"xiaoyu.zhu@1hai.cn\">"
  },
  {
    "path": "lib-media-recorder/src/main/recorder/com/sharry/lib/media/recorder/AudioRecorder.java",
    "chars": 6960,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport a"
  },
  {
    "path": "lib-media-recorder/src/main/recorder/com/sharry/lib/media/recorder/BaseMediaRecorder.java",
    "chars": 3184,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimp"
  },
  {
    "path": "lib-media-recorder/src/main/recorder/com/sharry/lib/media/recorder/VideoRecorder.java",
    "chars": 9092,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.content.Context;\nimport android.media.MediaCodec;\nimport android."
  },
  {
    "path": "lib-media-recorder/src/main/utils/com/sharry/lib/media/recorder/AVPoolExecutor.java",
    "chars": 3345,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport java.util."
  },
  {
    "path": "lib-media-recorder/src/main/utils/com/sharry/lib/media/recorder/FileUtil.java",
    "chars": 8913,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentResolver;\nimp"
  },
  {
    "path": "lib-media-recorder/src/main/utils/com/sharry/lib/media/recorder/NetworkUtil.java",
    "chars": 6262,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.Manifest;\nimport android.content.Context;\nimport android.net.Conn"
  },
  {
    "path": "lib-media-recorder/src/main/utils/com/sharry/lib/media/recorder/VersionUtil.java",
    "chars": 553,
    "preview": "package com.sharry.lib.media.recorder;\n\nimport android.os.Build;\n\n/**\n * 版本控制相关的工具类\n *\n * @author Sharry <a href=\"Sharry"
  },
  {
    "path": "lib-opengles/.gitignore",
    "chars": 491,
    "preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated file"
  },
  {
    "path": "lib-opengles/build.gradle",
    "chars": 714,
    "preview": "apply plugin: 'com.android.library'\napply plugin: 'com.github.dcendents.android-maven'\n\ngroup = 'com.github.SharryChoo'\n"
  },
  {
    "path": "lib-opengles/proguard-rules.pro",
    "chars": 751,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "lib-opengles/src/main/AndroidManifest.xml",
    "chars": 47,
    "preview": "<manifest package=\"com.sharry.lib.opengles\" />\n"
  },
  {
    "path": "lib-opengles/src/main/java/com/sharry/lib/opengles/surface/ContextSharedGLSurfaceView.java",
    "chars": 1780,
    "preview": "package com.sharry.lib.opengles.surface;\n\nimport android.content.Context;\nimport android.opengl.GLSurfaceView;\nimport an"
  },
  {
    "path": "lib-opengles/src/main/java/com/sharry/lib/opengles/texture/GLTextureView.java",
    "chars": 11673,
    "preview": "package com.sharry.lib.opengles.texture;\n\nimport android.content.Context;\nimport android.graphics.SurfaceTexture;\nimport"
  },
  {
    "path": "lib-opengles/src/main/java/com/sharry/lib/opengles/texture/ITextureRenderer.java",
    "chars": 459,
    "preview": "package com.sharry.lib.opengles.texture;\n\nimport androidx.annotation.WorkerThread;\n\n/**\n * OpenGL ES 基础的 Texture Rendere"
  },
  {
    "path": "lib-opengles/src/main/java/com/sharry/lib/opengles/util/EglCore.java",
    "chars": 7949,
    "preview": "package com.sharry.lib.opengles.util;\n\nimport android.graphics.SurfaceTexture;\nimport android.opengl.EGL14;\nimport andro"
  },
  {
    "path": "lib-opengles/src/main/java/com/sharry/lib/opengles/util/FboHelper.java",
    "chars": 3112,
    "preview": "package com.sharry.lib.opengles.util;\n\nimport android.opengl.GLES20;\n\nimport com.sharry.lib.opengles.texture.ITextureRen"
  },
  {
    "path": "lib-opengles/src/main/java/com/sharry/lib/opengles/util/GlMatrixUtil.java",
    "chars": 3754,
    "preview": "package com.sharry.lib.opengles.util;\n\nimport android.opengl.Matrix;\n\n/**\n * 用于快捷构建 GL 的 Matrix\n *\n * @author zhuxiaoyu "
  },
  {
    "path": "lib-opengles/src/main/java/com/sharry/lib/opengles/util/GlUtil.java",
    "chars": 6276,
    "preview": "package com.sharry.lib.opengles.util;\n\nimport android.content.Context;\nimport android.opengl.GLES11Ext;\nimport android.o"
  },
  {
    "path": "lib-opengles/src/main/utils/com/sharry/lib/opengles/EglCore.java",
    "chars": 7711,
    "preview": "package com.sharry.lib.opengles;\n\nimport android.graphics.SurfaceTexture;\nimport android.opengl.EGL14;\nimport android.op"
  },
  {
    "path": "lib-opengles/src/main/utils/com/sharry/lib/opengles/GlUtil.java",
    "chars": 3321,
    "preview": "package com.sharry.lib.opengles;\n\nimport android.content.Context;\nimport android.opengl.GLES20;\n\nimport java.io.Buffered"
  },
  {
    "path": "lib-scamera/.gitignore",
    "chars": 491,
    "preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated file"
  }
]

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

About this extraction

This page contains the full source code of the SharryChoo/SAlbum GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 228 files (709.2 KB), approximately 174.9k tokens, and a symbol index with 1665 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!