Full Code of liuguangqiang/IPicker for AI

master db643cff3b82 cached
58 files
131.0 KB
31.9k tokens
260 symbols
1 requests
Download .txt
Repository: liuguangqiang/IPicker
Branch: master
Commit: db643cff3b82
Files: 58
Total size: 131.0 KB

Directory structure:
gitextract_0zrh03zi/

├── .gitignore
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── liuguangqiang/
│       │               └── ipicker/
│       │                   └── sample/
│       │                       └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── liuguangqiang/
│       │   │           └── ipicker/
│       │   │               └── sample/
│       │   │                   ├── MainActivity.java
│       │   │                   └── SelectedAdapter.java
│       │   └── res/
│       │       ├── layout/
│       │       │   ├── activity_main.xml
│       │       │   └── item_selected.xml
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── strings.xml
│       │       │   └── styles.xml
│       │       └── values-w820dp/
│       │           └── dimens.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── liuguangqiang/
│                       └── ipicker/
│                           └── sample/
│                               └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   ├── gradle-mvn-push.gradle
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── liuguangqiang/
│       │               └── ipicker/
│       │                   └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── liuguangqiang/
│       │   │           └── ipicker/
│       │   │               ├── CropImageActivity.java
│       │   │               ├── IPicker.java
│       │   │               ├── IPickerActivity.java
│       │   │               ├── adapters/
│       │   │               │   ├── BaseAdapter.java
│       │   │               │   └── PhotosAdapter.java
│       │   │               ├── crop/
│       │   │               │   ├── Crop.java
│       │   │               │   ├── CropImageView.java
│       │   │               │   ├── CropUtil.java
│       │   │               │   ├── HighlightView.java
│       │   │               │   ├── ImageViewTouchBase.java
│       │   │               │   ├── Log.java
│       │   │               │   ├── MonitoredActivity.java
│       │   │               │   └── RotateBitmap.java
│       │   │               ├── entities/
│       │   │               │   └── Photo.java
│       │   │               ├── internal/
│       │   │               │   ├── ImageMedia.java
│       │   │               │   └── Logger.java
│       │   │               └── widgets/
│       │   │                   └── SquareRelativeLayout.java
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── crop_texture.xml
│       │       │   └── round_white.xml
│       │       ├── layout/
│       │       │   ├── activity_ipicker.xml
│       │       │   ├── activity_ipicker_crop.xml
│       │       │   └── item_photo.xml
│       │       ├── values/
│       │       │   ├── attrs.xml
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   └── strings.xml
│       │       └── values-zh/
│       │           └── strings.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── liuguangqiang/
│                       └── ipicker/
│                           └── ExampleUnitTest.java
└── settings.gradle

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

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

# Files for the Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/

# Gradle files
.gradle/
build/

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

# Proguard folder generated by Eclipse
proguard/

#Eclipse
.project
.classpath
.settings

#OSX
.DS_Store

#Android Studio
build/

# Intellij project files
*.iml
*.ipr
*.iws
.idea/

#gradle
.gradle/


================================================
FILE: README.md
================================================
IPicker
======================================
A material design style pictures selector.

## Screenshot
<img src="arts/1.png" width="30%"> <img src="arts/2.png" width="30%"> <img src="arts/clipping.png" width="30%">

## Usage
### Gradle

```
dependencies {
   	implementation 'com.liuguangqiang.ipicker:IPicker:1.1.0'
}
```

### Maven
```
<dependency>
  <groupId>com.liuguangqiang.ipicker</groupId>
  <artifactId>IPicker</artifactId>
  <version>1.1.0</version>
  <type>pom</type>
</dependency>
```

### Manifest

```
<activity
     android:name="com.liuguangqiang.ipicker.IPickerActivity"
     android:screenOrientation="portrait"
     android:theme="@style/IPickerTheme" />
```

### Theme
```
<style name="IPickerTheme" parent="Theme.AppCompat.Light.DarkActionBar">
     <item name="colorPrimary">@color/color_primary</item>
     <item name="colorPrimaryDark">@color/color_primary_dark</item>
     <item name="colorAccent">@color/color_primary</item>
 </style>
```

### Open the picker
```java
IPicker.setLimit(1);
IPicker.open(context);
```

Return the selected images by EventBus.

```
@Subscribe
public void onEvent(IPickerEvent event) {
}
```

Also support to get the selected images by a listener.

```
IPicker.setOnSelectedListener(new IPicker.OnSelectedListener() {

      @Override
      public void onSelected(List<String> paths) {}

});
```

## License

    Copyright 2016 Eric Liu

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

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

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

================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.0"
    defaultConfig {
        applicationId "com.liuguangqiang.ipicker.sample"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.github.liuguangqiang.permissionhelper:permissionhelper:0.1.0'
    implementation 'com.github.bumptech.glide:glide:4.11.0'

    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testImplementation 'junit:junit:4.12'
    implementation project(':library')
}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Eric/Dev/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 *;
#}


================================================
FILE: app/src/androidTest/java/com/liuguangqiang/ipicker/sample/ExampleInstrumentedTest.java
================================================
package com.liuguangqiang.ipicker.sample;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumentation test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("com.liuguangqiang.ipicker.sample", appContext.getPackageName());
    }
}


================================================
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.liuguangqiang.ipicker.sample">

    <uses-permission
        android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions" />

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

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <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/liuguangqiang/ipicker/sample/MainActivity.java
================================================
/*
 * Copyright 2016 Eric Liu
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.liuguangqiang.ipicker.sample;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.liuguangqiang.ipicker.IPicker;

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

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

/**
 * A Sample
 */
public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private SelectedAdapter adapter;
    private ArrayList<String> selectPictures = new ArrayList<>();

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

    private void initViews() {
        IPicker.setLimit(1);
        IPicker.setOnSelectedListener(new IPicker.OnSelectedListener() {
            @Override
            public void onSelected(List<String> paths) {
                selectPictures.clear();
                selectPictures.addAll(paths);
                adapter.notifyDataSetChanged();
            }
        });
        Button button = findViewById(R.id.open_picker);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                IPicker.open(getApplicationContext());
            }
        });
        adapter = new SelectedAdapter(getApplicationContext(), selectPictures);
        recyclerView = findViewById(R.id.rv_photos);
        recyclerView.setLayoutManager(new GridLayoutManager(getApplicationContext(), 4, GridLayoutManager.VERTICAL, false));
        recyclerView.setAdapter(adapter);
    }

}


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

package com.liuguangqiang.ipicker.sample;

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

import com.bumptech.glide.Glide;
import com.liuguangqiang.ipicker.adapters.BaseAdapter;

import java.util.List;

import androidx.recyclerview.widget.RecyclerView;

/**
 * Created by Eric on 16/9/12.
 */
public class SelectedAdapter extends BaseAdapter<String, SelectedAdapter.ViewHolder> {

    public SelectedAdapter(Context context, List<String> data) {
        super(context, data);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(layoutInflater, parent, false);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        super.onBindViewHolder(holder, position);
        holder.bindData(data.get(position));
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        private ImageView ivPhoto;

        public ViewHolder(LayoutInflater layoutInflater, ViewGroup parent, boolean attachToRoot) {
            super(layoutInflater.inflate(R.layout.item_selected, parent, attachToRoot));
            ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo);
        }

        public void bindData(String path) {
            Glide.with(itemView.getContext()).load(path).into(ivPhoto);
        }

    }

}


================================================
FILE: app/src/main/res/layout/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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.liuguangqiang.ipicker.sample.MainActivity">

    <Button
        android:id="@+id/open_picker"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="选择照片" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_photos"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</LinearLayout>


================================================
FILE: app/src/main/res/layout/item_selected.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<com.liuguangqiang.ipicker.widgets.SquareRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_photo"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop" />

</com.liuguangqiang.ipicker.widgets.SquareRelativeLayout>

================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>


================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
</resources>


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


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

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

</resources>


================================================
FILE: app/src/main/res/values-w820dp/dimens.xml
================================================
<resources>
    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
         (such as screen margins) for screens with more than 820dp of available width. This
         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
    <dimen name="activity_horizontal_margin">64dp</dimen>
</resources>


================================================
FILE: app/src/test/java/com/liuguangqiang/ipicker/sample/ExampleUnitTest.java
================================================
package com.liuguangqiang.ipicker.sample;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

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

buildscript {
    repositories {
        jcenter()

        google()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.6.2'
        classpath 'com.novoda:bintray-release:0.9.2'
    }
}

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

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


================================================
FILE: gradle/gradle-mvn-push.gradle
================================================
apply plugin: 'maven'
apply plugin: 'signing'

def isReleaseBuild() {
    return VERSION_NAME.contains("SNAPSHOT") == false
}

def getReleaseRepositoryUrl() {
    return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
            : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}

def getSnapshotRepositoryUrl() {
    return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
            : "https://oss.sonatype.org/content/repositories/snapshots/"
}

def getRepositoryUsername() {
    return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}

def getRepositoryPassword() {
    return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}

afterEvaluate { project ->
    uploadArchives {
        repositories {
            mavenDeployer {
                beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

                pom.groupId = GROUP
                pom.artifactId = POM_ARTIFACT_ID
                pom.version = VERSION_NAME

                repository(url: getReleaseRepositoryUrl()) {
                    authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
                }
                snapshotRepository(url: getSnapshotRepositoryUrl()) {
                    authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
                }

                pom.project {
                    name POM_NAME
                    packaging POM_PACKAGING
                    description POM_DESCRIPTION
                    url POM_URL

                    scm {
                        url POM_SCM_URL
                        connection POM_SCM_CONNECTION
                        developerConnection POM_SCM_DEV_CONNECTION
                    }

                    licenses {
                        license {
                            name POM_LICENCE_NAME
                            url POM_LICENCE_URL
                            distribution POM_LICENCE_DIST
                        }
                    }

                    developers {
                        developer {
                            id POM_DEVELOPER_ID
                            name POM_DEVELOPER_NAME
                        }
                    }
                }
            }
        }
    }

    signing {
        required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
        sign configurations.archives
    }

    task androidJavadocs(type: Javadoc) {
        source = android.sourceSets.main.java.srcDirs
        classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
    }

    task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
        classifier = 'javadoc'
        from androidJavadocs.destinationDir
    }

    task androidSourcesJar(type: Jar) {
        classifier = 'sources'
        from android.sourceSets.main.java.sourceFiles
    }

    artifacts {
        archives androidSourcesJar
//        archives androidJavadocsJar
    }
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Apr 08 10:04:11 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-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.
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true

# 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

VERSION_NAME=1.0.1
VERSION_CODE=101
GROUP=com.liuguangqiang.ipicker

POM_NAME=IPicker
POM_ARTIFACT_ID=library
POM_PACKAGING=aar

POM_DESCRIPTION=A material design style pictures selector.

POM_URL=https://github.com/liuguangqiang/IPicker
POM_SCM_URL=https://github.com/liuguangqiang/IPicker
POM_SCM_CONNECTION=https://github.com/liuguangqiang/IPicker.git
POM_SCM_DEV_CONNECTION=https://github.com/liuguangqiang/IPicker.git

POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo

POM_DEVELOPER_ID=EricLiu
POM_DEVELOPER_NAME=EricLiu

================================================
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: library/.gitignore
================================================
/build


================================================
FILE: library/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'com.novoda.bintray-release'
apply from: rootProject.file('gradle/gradle-mvn-push.gradle')

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.0"

    lintOptions {
        abortOnError false
    }

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

publish {
    userOrg = 'liuguangqiang'
    groupId = 'com.liuguangqiang.ipicker'
    artifactId = 'IPicker'
    publishVersion = '1.1.0'
    desc = 'A material design style pictures selector.'
    website = 'https://github.com/liuguangqiang/IPicker'
}

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

    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.github.liuguangqiang.permissionhelper:permissionhelper:0.1.0'
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    testImplementation 'junit:junit:4.12'
}


================================================
FILE: library/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Eric/Dev/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 *;
#}


================================================
FILE: library/src/androidTest/java/com/liuguangqiang/ipicker/ExampleInstrumentedTest.java
================================================
package com.liuguangqiang.ipicker;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumentation test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("com.liuguangqiang.ipicker.test", appContext.getPackageName());
    }
}


================================================
FILE: library/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.liuguangqiang.ipicker">

    <uses-permission
        android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions" />

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

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

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

    <application
        android:allowBackup="true"
        android:supportsRtl="true">

        <activity
            android:name=".IPickerActivity"
            android:screenOrientation="portrait" />

        <activity
            android:name=".CropImageActivity"
            android:screenOrientation="portrait" />

    </application>

</manifest>


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

package com.liuguangqiang.ipicker;

import android.annotation.TargetApi;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.opengl.GLES10;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager;

import com.liuguangqiang.ipicker.crop.Crop;
import com.liuguangqiang.ipicker.crop.CropImageView;
import com.liuguangqiang.ipicker.crop.CropUtil;
import com.liuguangqiang.ipicker.crop.HighlightView;
import com.liuguangqiang.ipicker.crop.ImageViewTouchBase;
import com.liuguangqiang.ipicker.crop.Log;
import com.liuguangqiang.ipicker.crop.MonitoredActivity;
import com.liuguangqiang.ipicker.crop.RotateBitmap;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.CountDownLatch;

/*
 * Modified from original in AOSP.
 */
public class CropImageActivity extends MonitoredActivity {

    private static final int ACTION_DONE = 0;

    private static final int SIZE_DEFAULT = 2048;
    private static final int SIZE_LIMIT = 4096;

    private final Handler handler = new Handler();

    private int aspectX;
    private int aspectY;

    // Output image
    private int maxX;
    private int maxY;
    private int exifRotation;
    private boolean saveAsPng;

    private Uri sourceUri;
    private Uri saveUri;

    private boolean isSaving;

    private int sampleSize;
    private RotateBitmap rotateBitmap;
    private CropImageView imageView;
    private HighlightView cropView;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setupWindowFlags();
        setupViews();

        loadInput();
        if (rotateBitmap == null) {
            finish();
            return;
        }
        startCrop();

        initToolbar();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, ACTION_DONE, 0, R.string.action_done).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            case ACTION_DONE:
                onSaveClicked();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }


    private void initToolbar() {
        setTitle(R.string.crop__title);
        getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_close_white);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setHomeButtonEnabled(true);
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private void setupWindowFlags() {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }

    private void setupViews() {
        setContentView(R.layout.activity_ipicker_crop);

        imageView = (CropImageView) findViewById(R.id.ipicker_crop_image);
        imageView.context = this;
        imageView.setRecycler(new ImageViewTouchBase.Recycler() {
            @Override
            public void recycle(Bitmap b) {
                b.recycle();
                System.gc();
            }
        });
    }

    private void loadInput() {
        Intent intent = getIntent();
        Bundle extras = intent.getExtras();

        if (extras != null) {
            aspectX = extras.getInt(Crop.Extra.ASPECT_X);
            aspectY = extras.getInt(Crop.Extra.ASPECT_Y);
            maxX = extras.getInt(Crop.Extra.MAX_X);
            maxY = extras.getInt(Crop.Extra.MAX_Y);
            saveAsPng = extras.getBoolean(Crop.Extra.AS_PNG, false);
            saveUri = extras.getParcelable(MediaStore.EXTRA_OUTPUT);
        }

        sourceUri = intent.getData();
        if (sourceUri != null) {
            exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));

            InputStream is = null;
            try {
                sampleSize = calculateBitmapSampleSize(sourceUri);
                is = getContentResolver().openInputStream(sourceUri);
                BitmapFactory.Options option = new BitmapFactory.Options();
                option.inSampleSize = sampleSize;
                rotateBitmap = new RotateBitmap(BitmapFactory.decodeStream(is, null, option), exifRotation);
            } catch (IOException e) {
                Log.e("Error reading image: " + e.getMessage(), e);
                setResultException(e);
            } catch (OutOfMemoryError e) {
                Log.e("OOM reading image: " + e.getMessage(), e);
                setResultException(e);
            } finally {
                CropUtil.closeSilently(is);
            }
        }
    }

    private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {
        InputStream is = null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        try {
            is = getContentResolver().openInputStream(bitmapUri);
            BitmapFactory.decodeStream(is, null, options); // Just get image size
        } finally {
            CropUtil.closeSilently(is);
        }

        int maxSize = getMaxImageSize();
        int sampleSize = 1;
        while (options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize) {
            sampleSize = sampleSize << 1;
        }
        return sampleSize;
    }

    private int getMaxImageSize() {
        int textureLimit = getMaxTextureSize();
        if (textureLimit == 0) {
            return SIZE_DEFAULT;
        } else {
            return Math.min(textureLimit, SIZE_LIMIT);
        }
    }

    private int getMaxTextureSize() {
        // The OpenGL texture size is the maximum size that can be drawn in an ImageView
        int[] maxSize = new int[1];
        GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
        return maxSize[0];
    }

    private void startCrop() {
        if (isFinishing()) {
            return;
        }
        imageView.setImageRotateBitmapResetBase(rotateBitmap, true);
        CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__wait),
                new Runnable() {
                    public void run() {
                        final CountDownLatch latch = new CountDownLatch(1);
                        handler.post(new Runnable() {
                            public void run() {
                                if (imageView.getScale() == 1F) {
                                    imageView.center();
                                }
                                latch.countDown();
                            }
                        });
                        try {
                            latch.await();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        new Cropper().crop();
                    }
                }, handler
        );
    }

    private class Cropper {

        private void makeDefault() {
            if (rotateBitmap == null) {
                return;
            }

            HighlightView hv = new HighlightView(imageView);
            final int width = rotateBitmap.getWidth();
            final int height = rotateBitmap.getHeight();

            Rect imageRect = new Rect(0, 0, width, height);

            // Make the default size about 4/5 of the width or height
            int cropWidth = Math.min(width, height) * 4 / 5;
            @SuppressWarnings("SuspiciousNameCombination")
            int cropHeight = cropWidth;

            if (aspectX != 0 && aspectY != 0) {
                if (aspectX > aspectY) {
                    cropHeight = cropWidth * aspectY / aspectX;
                } else {
                    cropWidth = cropHeight * aspectX / aspectY;
                }
            }

            int x = (width - cropWidth) / 2;
            int y = (height - cropHeight) / 2;

            RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
            hv.setup(imageView.getUnrotatedMatrix(), imageRect, cropRect, aspectX != 0 && aspectY != 0);
            imageView.add(hv);
        }

        public void crop() {
            handler.post(new Runnable() {
                public void run() {
                    makeDefault();
                    imageView.invalidate();
                    if (imageView.highlightViews.size() == 1) {
                        cropView = imageView.highlightViews.get(0);
                        cropView.setFocus(true);
                    }
                }
            });
        }
    }

    private void onSaveClicked() {
        if (cropView == null || isSaving) {
            return;
        }
        isSaving = true;

        Bitmap croppedImage;
        Rect r = cropView.getScaledCropRect(sampleSize);
        int width = r.width();
        int height = r.height();

        int outWidth = width;
        int outHeight = height;
        if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
            float ratio = (float) width / (float) height;
            if ((float) maxX / (float) maxY > ratio) {
                outHeight = maxY;
                outWidth = (int) ((float) maxY * ratio + .5f);
            } else {
                outWidth = maxX;
                outHeight = (int) ((float) maxX / ratio + .5f);
            }
        }

        try {
            croppedImage = decodeRegionCrop(r, outWidth, outHeight);
        } catch (IllegalArgumentException e) {
            setResultException(e);
            finish();
            return;
        }

        if (croppedImage != null) {
            imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
            imageView.center();
            imageView.highlightViews.clear();
        }
        saveImage(croppedImage);
    }

    private void saveImage(Bitmap croppedImage) {
        if (croppedImage != null) {
            final Bitmap b = croppedImage;
            CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving),
                    new Runnable() {
                        public void run() {
                            saveOutput(b);
                        }
                    }, handler
            );
        } else {
            finish();
        }
    }

    private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
        // Release memory now
        clearImageView();

        InputStream is = null;
        Bitmap croppedImage = null;
        try {
            is = getContentResolver().openInputStream(sourceUri);
            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
            final int width = decoder.getWidth();
            final int height = decoder.getHeight();

            if (exifRotation != 0) {
                // Adjust crop area to account for image rotation
                Matrix matrix = new Matrix();
                matrix.setRotate(-exifRotation);

                RectF adjusted = new RectF();
                matrix.mapRect(adjusted, new RectF(rect));

                // Adjust to account for origin at 0,0
                adjusted.offset(adjusted.left < 0 ? width : 0, adjusted.top < 0 ? height : 0);
                rect = new Rect((int) adjusted.left, (int) adjusted.top, (int) adjusted.right, (int) adjusted.bottom);
            }

            try {
                croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
                if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {
                    Matrix matrix = new Matrix();
                    matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
                    croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
                }
            } catch (IllegalArgumentException e) {
                // Rethrow with some extra information
                throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image ("
                        + width + "," + height + "," + exifRotation + ")", e);
            }

        } catch (IOException e) {
            Log.e("Error cropping image: " + e.getMessage(), e);
            setResultException(e);
        } catch (OutOfMemoryError e) {
            Log.e("OOM cropping image: " + e.getMessage(), e);
            setResultException(e);
        } finally {
            CropUtil.closeSilently(is);
        }
        return croppedImage;
    }

    private void clearImageView() {
        imageView.clear();
        if (rotateBitmap != null) {
            rotateBitmap.recycle();
        }
        System.gc();
    }

    private void saveOutput(Bitmap croppedImage) {
        if (saveUri != null) {
            OutputStream outputStream = null;
            try {
                outputStream = getContentResolver().openOutputStream(saveUri);
                if (outputStream != null) {
                    croppedImage.compress(saveAsPng ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG,
                            90,     // note: quality is ignored when using PNG
                            outputStream);
                }
            } catch (IOException e) {
                setResultException(e);
                Log.e("Cannot open file: " + saveUri, e);
            } finally {
                CropUtil.closeSilently(outputStream);
            }

            CropUtil.copyExifRotation(
                    CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
                    CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
            );

            setResultUri(saveUri);
        }

        final Bitmap b = croppedImage;
        handler.post(new Runnable() {
            public void run() {
                imageView.clear();
                b.recycle();
            }
        });

        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (rotateBitmap != null) {
            rotateBitmap.recycle();
        }
    }

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

    public boolean isSaving() {
        return isSaving;
    }

    private void setResultUri(Uri uri) {
        setResult(RESULT_OK, new Intent().putExtra(MediaStore.EXTRA_OUTPUT, uri));
    }

    private void setResultException(Throwable throwable) {
        setResult(Crop.RESULT_ERROR, new Intent().putExtra(Crop.Extra.ERROR, throwable));
    }

}


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

package com.liuguangqiang.ipicker;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

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

/**
 * Created by Eric on 16/9/18.
 */
public class IPicker {

    /**
     * Limit the count of picture selected.
     */
    private static int limit = 1;

    private static boolean cropEnable = false;

    private static OnSelectedListener onSelectedListener;

    private IPicker() {
    }

    /**
     * Limit the count of picture selected.
     *
     * @param limit
     */
    public static void setLimit(int limit) {
        IPicker.limit = limit;
    }

    public static void setCropEnable(boolean cropEnable) {
        IPicker.cropEnable = cropEnable;
    }

    public static void setOnSelectedListener(OnSelectedListener listener) {
        IPicker.onSelectedListener = listener;
    }

    /**
     * Finish the IPicker and post a event to observer.
     *
     * @param path
     */
    public static void finish(String path) {
        List<String> paths = new ArrayList<>();
        paths.add(path);
        finish(paths);
    }

    /**
     * Finish the IPicker and post a event to observer.
     *
     * @param paths
     */
    public static void finish(List<String> paths) {
        if (onSelectedListener != null) {
            onSelectedListener.onSelected(paths);
        }
    }

    /**
     * Open the IPicker to select photos or take pictures.
     *
     * @param context
     */
    public static void open(Context context) {
        open(context, null);
    }

    /**
     * Open the IPicker to select photos or take pictures.
     *
     * @param context
     * @param selected
     */
    public static void open(Context context, ArrayList<String> selected) {
        Intent intent = new Intent(context, IPickerActivity.class);
        if (!(context instanceof Activity)) {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        Bundle bundle = new Bundle();
        bundle.putInt(IPickerActivity.ARG_LIMIT, limit);
        bundle.putBoolean(IPickerActivity.ARG_CROP_ENABLE, cropEnable);
        if (selected != null && !selected.isEmpty()) {
            bundle.putStringArrayList(IPickerActivity.ARG_SELECTED, selected);

        }
        intent.putExtras(bundle);
        context.startActivity(intent);
    }

    public interface OnSelectedListener {

        void onSelected(List<String> paths);
    }
}


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

package com.liuguangqiang.ipicker;

import android.Manifest;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.RelativeLayout;

import com.google.android.material.snackbar.Snackbar;
import com.liuguangqiang.ipicker.adapters.BaseAdapter;
import com.liuguangqiang.ipicker.adapters.PhotosAdapter;
import com.liuguangqiang.ipicker.crop.Crop;
import com.liuguangqiang.ipicker.entities.Photo;
import com.liuguangqiang.ipicker.internal.ImageMedia;
import com.liuguangqiang.permissionhelper.PermissionHelper;
import com.liuguangqiang.permissionhelper.annotations.PermissionDenied;
import com.liuguangqiang.permissionhelper.annotations.PermissionGranted;

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

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

/**
 * Created by Eric on 16/9/12.
 */
public class IPickerActivity extends AppCompatActivity implements BaseAdapter.OnItemClickListener {

    public static final String ARG_SELECTED = "ARG_SELECTED";
    public static final String ARG_LIMIT = "ARG_LIMIT";
    public static final String ARG_CROP_ENABLE = "ARG_CROP_ENABLE";

    private static final int REQUEST_CAMERA = 1;
    private static final int ACTION_DONE = 0;

    private RelativeLayout layoutContainer;
    private RecyclerView recyclerView;
    private PhotosAdapter adapter;
    private List<Photo> photoList = new ArrayList<>();
    private int limit = 1;
    private boolean cropEnable = false;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ipicker);
        initToolbar();
        initViews();
        getArguments();
        requestPhotos();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            case ACTION_DONE:
                IPicker.finish(adapter.getSelected());
                finish();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, ACTION_DONE, 0, R.string.action_done).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        menu.findItem(ACTION_DONE).setVisible(limit > 1 ? true : false);
        return super.onPrepareOptionsMenu(menu);
    }

    private void initToolbar() {
        setTitle(R.string.title_act_picker);
        getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_close_white);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setHomeButtonEnabled(true);
    }

    private void initViews() {
        getWindow().setBackgroundDrawable(null);
        recyclerView = findViewById(R.id.rv_photos);
        recyclerView.setLayoutManager(new GridLayoutManager(getApplicationContext(), 4, GridLayoutManager.VERTICAL, false));
        adapter = new PhotosAdapter(this, photoList);
        adapter.setOnItemClickListener(this);
        recyclerView.setAdapter(adapter);
        layoutContainer = findViewById(R.id.layout_container);
    }

    private void getArguments() {
        Bundle bundle = getIntent().getExtras();
        if (bundle != null) {
            if (bundle.containsKey(ARG_LIMIT)) {
                limit = bundle.getInt(ARG_LIMIT);
                if (limit == 0) limit = 1;

                adapter.setSingleSelection(isSingleSelection());
            }

            if (bundle.containsKey(ARG_SELECTED)) {
                adapter.addSelected(bundle.getStringArrayList(ARG_SELECTED));
                updateTitle();
            }

            if (bundle.containsKey(ARG_CROP_ENABLE)) {
                cropEnable = bundle.getBoolean(ARG_CROP_ENABLE, false);
            }
        }
    }

    private boolean isSingleSelection() {
        return limit == 1;
    }

    @Override
    public void onItemClick(View view, int position) {
        if (position == 0) {
            requestCamera();
        } else if (isSingleSelection()) {
            if (cropEnable) {
                cropImage(Uri.parse("file://" + photoList.get(position).path));
            } else {
                IPicker.finish(photoList.get(position).path);
                finish();
            }
        } else if (adapter.isSelected(photoList.get(position).path)) {
            removeSelected(position);
        } else {
            addSelected(position);
        }
    }

    private void removeSelected(int position) {
        adapter.removeSelected(photoList.get(position));
        adapter.notifyItemChanged(position);
        updateTitle();
    }

    private void addSelected(int position) {
        if ((adapter.getSelectedTotal() < limit)) {
            adapter.addSelected(photoList.get(position));
            adapter.notifyItemChanged(position);
            updateTitle();
        } else {
            Snackbar.make(layoutContainer, getString(R.string.format_max_size, limit), Snackbar.LENGTH_LONG).show();
        }
    }

    private void updateTitle() {
        if (limit > 1) {
            if (adapter.getSelectedTotal() == 0) {
                setTitle(R.string.title_act_picker);
            } else {
                setTitle("" + adapter.getSelectedTotal());
            }
        }
    }

    private void requestPhotos() {
        PermissionHelper.getInstance().requestPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
    }

    private void requestCamera() {
        PermissionHelper.getInstance().requestPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        PermissionHelper.getInstance().onRequestPermissionsResult(this, permissions, grantResults);
    }

    @PermissionGranted(permission = Manifest.permission.READ_EXTERNAL_STORAGE)
    public void galleryGranted() {
        getPhotos();
    }

    @PermissionDenied(permission = Manifest.permission.READ_EXTERNAL_STORAGE)
    public void galleryDenied() {
        promptNoPermission(R.string.no_permission_gallery);
    }

    @PermissionGranted(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)
    public void cameraGranted() {
        takePicture();
    }

    @PermissionDenied(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)
    public void cameraDenied() {
        promptNoPermission(R.string.no_permission_camera);
    }

    private void getPhotos() {
        photoList.add(new Photo(true));
        photoList.addAll(ImageMedia.queryAll(this));
        adapter.notifyDataSetChanged();
    }

    private Uri tempUri;

    private void deleteTemp() {
        if (tempUri != null) {
            getContentResolver().delete(tempUri, null, null);
        }
    }

    private void takePicture() {
        ContentValues values = new ContentValues(1);
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
        tempUri = getContentResolver()
                .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        if (tempUri != null) {
            intent.putExtra(MediaStore.EXTRA_OUTPUT, tempUri);
            intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
        }
        startActivityForResult(intent, REQUEST_CAMERA);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case REQUEST_CAMERA:
                    if (isSingleSelection()) {
                        if (cropEnable) {
                            cropImage(tempUri);
                        } else {
                            IPicker.finish(ImageMedia.getFilePath(getApplicationContext(), tempUri));
                            finish();
                        }
                    } else {
                        String path = ImageMedia.getFilePath(getApplicationContext(), tempUri);
                        Photo photo = new Photo(path);
                        photoList.add(1, photo);
                        if (adapter.getSelectedTotal() < limit) {
                            adapter.addSelected(photo);
                        }
                        adapter.notifyDataSetChanged();
                        updateTitle();
                    }
                    break;
                case Crop.REQUEST_CROP:
                    IPicker.finish(Crop.getOutput(data).toString());
                    finish();
                    break;
            }
        } else if (resultCode == Activity.RESULT_CANCELED) {
            switch (requestCode) {
                case REQUEST_CAMERA:
                    deleteTemp();
                    break;
            }
        }
    }

    private void promptNoPermission(@StringRes int res) {
        Snackbar.make(layoutContainer, res, Snackbar.LENGTH_LONG).setAction(R.string.btn_setting, new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                        Uri.parse("package:" + getPackageName()));
                startActivity(intent);
            }
        }).show();
    }

    private void cropImage(Uri source) {
        Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped" + System.currentTimeMillis()));
        Crop.of(source, destination).asSquare().start(this);
    }
}


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

package com.liuguangqiang.ipicker.adapters;

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

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

import androidx.recyclerview.widget.RecyclerView;

/**
 * Created by Eric on 15/7/7.
 */
public abstract class BaseAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {

    public Context mContext;
    public List<T> data = new ArrayList<>();
    public final LayoutInflater layoutInflater;

    public OnItemClickListener onItemClickListener;
    public OnItemLongClickListener onItemLongClickListener;

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
        this.onItemLongClickListener = onItemLongClickListener;
    }

    public BaseAdapter(Context context, List<T> data) {
        this.mContext = context;
        this.data = data;
        this.layoutInflater = LayoutInflater.from(context);
    }

    public LayoutInflater getLayoutInflater() {
        return layoutInflater;
    }

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

    @Override
    public void onBindViewHolder(VH holder, final int position) {
        if (holder.itemView != null) {
            if (onItemClickListener != null) {
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        onItemClickListener.onItemClick(v, position);
                    }
                });
            }

            if (onItemLongClickListener != null) {
                holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        onItemLongClickListener.onItemLongClick(position);
                        return false;
                    }
                });
            }
        }
    }

    @Override
    public VH onCreateViewHolder(ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onViewRecycled(VH holder) {
        super.onViewRecycled(holder);
    }

    public interface OnItemClickListener {

        void onItemClick(View view, int position);

    }

    public interface OnItemLongClickListener {

        void onItemLongClick(int position);

    }

}


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

package com.liuguangqiang.ipicker.adapters;

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

import com.bumptech.glide.Glide;
import com.liuguangqiang.ipicker.R;
import com.liuguangqiang.ipicker.entities.Photo;

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

import androidx.recyclerview.widget.RecyclerView;

/**
 * Created by Eric on 16/9/12.
 */
public class PhotosAdapter extends BaseAdapter<Photo, PhotosAdapter.ViewHolder> {

    private List<String> selected = new ArrayList<>();
    private boolean isSingleSelection = true;

    public PhotosAdapter(Context context, List<Photo> data) {
        super(context, data);
    }

    public void setSingleSelection(boolean singleSelection) {
        this.isSingleSelection = singleSelection;
    }

    public void addSelected(List<String> list) {
        selected.addAll(list);
    }

    public void addSelected(Photo photo) {
        selected.add(photo.path);
    }

    public void removeSelected(Photo photo) {
        selected.remove(photo.path);
    }

    public List<String> getSelected() {
        return selected;
    }

    public boolean isSelected(String path) {
        return selected.contains(path);
    }

    public int getSelectedTotal() {
        return selected.size();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(layoutInflater, parent, false);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        super.onBindViewHolder(holder, position);
        holder.bindData(data.get(position), !isSingleSelection && isSelected(data.get(position).path));
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        private ImageView ivPhoto;
        private ImageView ivCamera;
        private ImageView ivCheckbox;

        public ViewHolder(LayoutInflater layoutInflater, ViewGroup parent, boolean attachToRoot) {
            super(layoutInflater.inflate(R.layout.item_photo, parent, attachToRoot));
            ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo);
            ivCamera = (ImageView) itemView.findViewById(R.id.iv_camera);
            ivCheckbox = (ImageView) itemView.findViewById(R.id.iv_checkbox);
        }

        public void bindData(Photo entity, boolean selected) {
            if (entity.showCamera) {
                ivPhoto.setVisibility(View.GONE);
                ivCamera.setVisibility(View.VISIBLE);
                ivCheckbox.setVisibility(View.GONE);
            } else {
                ivPhoto.setVisibility(View.VISIBLE);
                ivCamera.setVisibility(View.GONE);
                ivCheckbox.setVisibility(selected ? View.VISIBLE : View.GONE);
                Glide.with(itemView.getContext()).load(entity.path).into(ivPhoto);
            }
        }
    }
}


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

package com.liuguangqiang.ipicker.crop;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.widget.Toast;

import com.liuguangqiang.ipicker.CropImageActivity;
import com.liuguangqiang.ipicker.R;

/**
 * Builder for crop Intents and utils for handling result
 */
public class Crop {

    public static final int REQUEST_CROP = 6709;
    public static final int REQUEST_PICK = 9162;
    public static final int RESULT_ERROR = 404;

    public interface Extra {
        String ASPECT_X = "aspect_x";
        String ASPECT_Y = "aspect_y";
        String MAX_X = "max_x";
        String MAX_Y = "max_y";
        String AS_PNG = "as_png";
        String ERROR = "error";
    }

    private Intent cropIntent;

    /**
     * Create a crop Intent builder with source and destination image Uris
     *
     * @param source      Uri for image to crop
     * @param destination Uri for saving the cropped image
     */
    public static Crop of(Uri source, Uri destination) {
        return new Crop(source, destination);
    }

    private Crop(Uri source, Uri destination) {
        cropIntent = new Intent();
        cropIntent.setData(source);
        cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
    }

    /**
     * Set fixed aspect ratio for crop area
     *
     * @param x Aspect X
     * @param y Aspect Y
     */
    public Crop withAspect(int x, int y) {
        cropIntent.putExtra(Extra.ASPECT_X, x);
        cropIntent.putExtra(Extra.ASPECT_Y, y);
        return this;
    }

    /**
     * Crop area with fixed 1:1 aspect ratio
     */
    public Crop asSquare() {
        cropIntent.putExtra(Extra.ASPECT_X, 1);
        cropIntent.putExtra(Extra.ASPECT_Y, 1);
        return this;
    }

    /**
     * Set maximum crop size
     *
     * @param width  Max width
     * @param height Max height
     */
    public Crop withMaxSize(int width, int height) {
        cropIntent.putExtra(Extra.MAX_X, width);
        cropIntent.putExtra(Extra.MAX_Y, height);
        return this;
    }

    /**
     * Set whether to save the result as a PNG or not. Helpful to preserve alpha.
     *
     * @param asPng whether to save the result as a PNG or not
     */
    public Crop asPng(boolean asPng) {
        cropIntent.putExtra(Extra.AS_PNG, asPng);
        return this;
    }

    /**
     * Send the crop Intent from an Activity
     *
     * @param activity Activity to receive result
     */
    public void start(Activity activity) {
        start(activity, REQUEST_CROP);
    }

    /**
     * Send the crop Intent from an Activity with a custom request code
     *
     * @param activity    Activity to receive result
     * @param requestCode requestCode for result
     */
    public void start(Activity activity, int requestCode) {
        activity.startActivityForResult(getIntent(activity), requestCode);
    }

    /**
     * Send the crop Intent from a Fragment
     *
     * @param context  Context
     * @param fragment Fragment to receive result
     */
    public void start(Context context, Fragment fragment) {
        start(context, fragment, REQUEST_CROP);
    }

    /**
     * Send the crop Intent with a custom request code
     *
     * @param context     Context
     * @param fragment    Fragment to receive result
     * @param requestCode requestCode for result
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void start(Context context, Fragment fragment, int requestCode) {
        fragment.startActivityForResult(getIntent(context), requestCode);
    }

    /**
     * Get Intent to start crop Activity
     *
     * @param context Context
     * @return Intent for CropImageActivity
     */
    public Intent getIntent(Context context) {
        cropIntent.setClass(context, CropImageActivity.class);
        return cropIntent;
    }

    /**
     * Retrieve URI for cropped image, as set in the Intent builder
     *
     * @param result Output Image URI
     */
    public static Uri getOutput(Intent result) {
        return result.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
    }

    /**
     * Retrieve error that caused crop to fail
     *
     * @param result Result Intent
     * @return Throwable handled in CropImageActivity
     */
    public static Throwable getError(Intent result) {
        return (Throwable) result.getSerializableExtra(Extra.ERROR);
    }

    /**
     * Pick image from an Activity
     *
     * @param activity Activity to receive result
     */
    public static void pickImage(Activity activity) {
        pickImage(activity, REQUEST_PICK);
    }

    /**
     * Pick image from a Fragment
     *
     * @param context  Context
     * @param fragment Fragment to receive result
     */
    public static void pickImage(Context context, Fragment fragment) {
        pickImage(context, fragment, REQUEST_PICK);
    }

    /**
     * Pick image from an Activity with a custom request code
     *
     * @param activity    Activity to receive result
     * @param requestCode requestCode for result
     */
    public static void pickImage(Activity activity, int requestCode) {
        try {
            activity.startActivityForResult(getImagePicker(), requestCode);
        } catch (ActivityNotFoundException e) {
            showImagePickerError(activity);
        }
    }

    /**
     * Pick image from a Fragment with a custom request code
     *
     * @param context     Context
     * @param fragment    Fragment to receive result
     * @param requestCode requestCode for result
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static void pickImage(Context context, Fragment fragment, int requestCode) {
        try {
            fragment.startActivityForResult(getImagePicker(), requestCode);
        } catch (ActivityNotFoundException e) {
            showImagePickerError(context);
        }
    }

    private static Intent getImagePicker() {
        return new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
    }

    private static void showImagePickerError(Context context) {
        Toast.makeText(context.getApplicationContext(), R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
    }

}


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

package com.liuguangqiang.ipicker.crop;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;

import com.liuguangqiang.ipicker.CropImageActivity;

import java.util.ArrayList;

import androidx.annotation.NonNull;

public class CropImageView extends ImageViewTouchBase {

    public ArrayList<HighlightView> highlightViews = new ArrayList<HighlightView>();
    public HighlightView motionHighlightView;
    public Context context;

    private float lastX;
    private float lastY;
    private int motionEdge;
    private int validPointerId;

    public CropImageView(Context context) {
        super(context);
    }

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

    public CropImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (bitmapDisplayed.getBitmap() != null) {
            for (HighlightView hv : highlightViews) {

                hv.matrix.set(getUnrotatedMatrix());
                hv.invalidate();
                if (hv.hasFocus()) {
                    centerBasedOnHighlightView(hv);
                }
            }
        }
    }

    @Override
    protected void zoomTo(float scale, float centerX, float centerY) {
        super.zoomTo(scale, centerX, centerY);
        for (HighlightView hv : highlightViews) {
            hv.matrix.set(getUnrotatedMatrix());
            hv.invalidate();
        }
    }

    @Override
    protected void zoomIn() {
        super.zoomIn();
        for (HighlightView hv : highlightViews) {
            hv.matrix.set(getUnrotatedMatrix());
            hv.invalidate();
        }
    }

    @Override
    protected void zoomOut() {
        super.zoomOut();
        for (HighlightView hv : highlightViews) {
            hv.matrix.set(getUnrotatedMatrix());
            hv.invalidate();
        }
    }

    @Override
    protected void postTranslate(float deltaX, float deltaY) {
        super.postTranslate(deltaX, deltaY);
        for (HighlightView hv : highlightViews) {
            hv.matrix.postTranslate(deltaX, deltaY);
            hv.invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        CropImageActivity cropImageActivity = (CropImageActivity) context;
        if (cropImageActivity.isSaving()) {
            return false;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                for (HighlightView hv : highlightViews) {
                    int edge = hv.getHit(event.getX(), event.getY());
                    if (edge != HighlightView.GROW_NONE) {
                        motionEdge = edge;
                        motionHighlightView = hv;
                        lastX = event.getX();
                        lastY = event.getY();
                        // Prevent multiple touches from interfering with crop area re-sizing
                        validPointerId = event.getPointerId(event.getActionIndex());
                        motionHighlightView.setMode((edge == HighlightView.MOVE)
                                ? HighlightView.ModifyMode.Move
                                : HighlightView.ModifyMode.Grow);
                        break;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (motionHighlightView != null) {
                    centerBasedOnHighlightView(motionHighlightView);
                    motionHighlightView.setMode(HighlightView.ModifyMode.None);
                }
                motionHighlightView = null;
                center();
                break;
            case MotionEvent.ACTION_MOVE:
                if (motionHighlightView != null && event.getPointerId(event.getActionIndex()) == validPointerId) {
                    motionHighlightView.handleMotion(motionEdge, event.getX()
                            - lastX, event.getY() - lastY);
                    lastX = event.getX();
                    lastY = event.getY();
                }

                // If we're not zoomed then there's no point in even allowing the user to move the image around.
                // This call to center puts it back to the normalized location.
                if (getScale() == 1F) {
                    center();
                }
                break;
        }

        return true;
    }

    // Pan the displayed image to make sure the cropping rectangle is visible.
    private void ensureVisible(HighlightView hv) {
        Rect r = hv.drawRect;

        int panDeltaX1 = Math.max(0, getLeft() - r.left);
        int panDeltaX2 = Math.min(0, getRight() - r.right);

        int panDeltaY1 = Math.max(0, getTop() - r.top);
        int panDeltaY2 = Math.min(0, getBottom() - r.bottom);

        int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
        int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;

        if (panDeltaX != 0 || panDeltaY != 0) {
            panBy(panDeltaX, panDeltaY);
        }
    }

    // If the cropping rectangle's size changed significantly, change the
    // view's center and scale according to the cropping rectangle.
    private void centerBasedOnHighlightView(HighlightView hv) {
        Rect drawRect = hv.drawRect;

        float width = drawRect.width();
        float height = drawRect.height();

        float thisWidth = getWidth();
        float thisHeight = getHeight();

        float z1 = thisWidth / width * .6F;
        float z2 = thisHeight / height * .6F;

        float zoom = Math.min(z1, z2);
        zoom = zoom * this.getScale();
        zoom = Math.max(1F, zoom);

        if ((Math.abs(zoom - getScale()) / zoom) > .1) {
            float[] coordinates = new float[]{hv.cropRect.centerX(), hv.cropRect.centerY()};
            getUnrotatedMatrix().mapPoints(coordinates);
            zoomTo(zoom, coordinates[0], coordinates[1], 300F);
        }

        ensureVisible(hv);
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);
        for (HighlightView highlightView : highlightViews) {
            highlightView.draw(canvas);
        }
    }

    public void add(HighlightView hv) {
        highlightViews.add(hv);
        invalidate();
    }
}


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

package com.liuguangqiang.ipicker.crop;

import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.text.TextUtils;

import java.io.Closeable;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import androidx.annotation.Nullable;

/*
 * Modified from original in AOSP.
 */
public class CropUtil {

    private static final String SCHEME_FILE = "file";
    private static final String SCHEME_CONTENT = "content";

    public static void closeSilently(@Nullable Closeable c) {
        if (c == null) return;
        try {
            c.close();
        } catch (Throwable t) {
            // Do nothing
        }
    }

    public static int getExifRotation(File imageFile) {
        if (imageFile == null) return 0;
        try {
            ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
            // We only recognize a subset of orientation tag values
            switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    return 90;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    return 180;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    return 270;
                default:
                    return ExifInterface.ORIENTATION_UNDEFINED;
            }
        } catch (IOException e) {
            Log.e("Error getting Exif data", e);
            return 0;
        }
    }

    public static boolean copyExifRotation(File sourceFile, File destFile) {
        if (sourceFile == null || destFile == null) return false;
        try {
            ExifInterface exifSource = new ExifInterface(sourceFile.getAbsolutePath());
            ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath());
            exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, exifSource.getAttribute(ExifInterface.TAG_ORIENTATION));
            exifDest.saveAttributes();
            return true;
        } catch (IOException e) {
            Log.e("Error copying Exif data", e);
            return false;
        }
    }

    @Nullable
    public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) {
        if (uri == null) return null;

        if (SCHEME_FILE.equals(uri.getScheme())) {
            return new File(uri.getPath());
        } else if (SCHEME_CONTENT.equals(uri.getScheme())) {
            final String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME};
            Cursor cursor = null;
            try {
                cursor = resolver.query(uri, filePathColumn, null, null, null);
                if (cursor != null && cursor.moveToFirst()) {
                    final int columnIndex = (uri.toString().startsWith("content://com.google.android.gallery3d")) ?
                            cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) :
                            cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
                    // Picasa images on API 13+
                    if (columnIndex != -1) {
                        String filePath = cursor.getString(columnIndex);
                        if (!TextUtils.isEmpty(filePath)) {
                            return new File(filePath);
                        }
                    }
                }
            } catch (IllegalArgumentException e) {
                // Google Drive images
                return getFromMediaUriPfd(context, resolver, uri);
            } catch (SecurityException ignored) {
                // Nothing we can do
            } finally {
                if (cursor != null) cursor.close();
            }
        }
        return null;
    }

    private static String getTempFilename(Context context) throws IOException {
        File outputDir = context.getCacheDir();
        File outputFile = File.createTempFile("image", "tmp", outputDir);
        return outputFile.getAbsolutePath();
    }

    @Nullable
    private static File getFromMediaUriPfd(Context context, ContentResolver resolver, Uri uri) {
        if (uri == null) return null;

        FileInputStream input = null;
        FileOutputStream output = null;
        try {
            ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
            FileDescriptor fd = pfd.getFileDescriptor();
            input = new FileInputStream(fd);

            String tempFilename = getTempFilename(context);
            output = new FileOutputStream(tempFilename);

            int read;
            byte[] bytes = new byte[4096];
            while ((read = input.read(bytes)) != -1) {
                output.write(bytes, 0, read);
            }
            return new File(tempFilename);
        } catch (IOException ignored) {
            // Nothing we can do
        } finally {
            closeSilently(input);
            closeSilently(output);
        }
        return null;
    }

    public static void startBackgroundJob(MonitoredActivity activity,
                                          String title, String message, Runnable job, Handler handler) {
        // Make the progress dialog uncancelable, so that we can guarantee
        // the thread will be done before the activity getting destroyed
        ProgressDialog dialog = ProgressDialog.show(
                activity, title, message, true, false);
        new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
    }

    private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable {

        private final MonitoredActivity activity;
        private final ProgressDialog dialog;
        private final Runnable job;
        private final Handler handler;
        private final Runnable cleanupRunner = new Runnable() {
            public void run() {
                activity.removeLifeCycleListener(BackgroundJob.this);
                if (dialog.getWindow() != null) dialog.dismiss();
            }
        };

        public BackgroundJob(MonitoredActivity activity, Runnable job,
                             ProgressDialog dialog, Handler handler) {
            this.activity = activity;
            this.dialog = dialog;
            this.job = job;
            this.activity.addLifeCycleListener(this);
            this.handler = handler;
        }

        public void run() {
            try {
                job.run();
            } finally {
                handler.post(cleanupRunner);
            }
        }

        @Override
        public void onActivityDestroyed(MonitoredActivity activity) {
            // We get here only when the onDestroyed being called before
            // the cleanupRunner. So, run it now and remove it from the queue
            cleanupRunner.run();
            handler.removeCallbacks(cleanupRunner);
        }

        @Override
        public void onActivityStopped(MonitoredActivity activity) {
            dialog.hide();
        }

        @Override
        public void onActivityStarted(MonitoredActivity activity) {
            dialog.show();
        }
    }

}


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

package com.liuguangqiang.ipicker.crop;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
import android.util.TypedValue;
import android.view.View;

import com.liuguangqiang.ipicker.R;

/*
 * Modified from code in AOSP.
 *
 * This class is used to display a highlighted cropping rectangle
 * overlayed on the image. There are two coordinate spaces in use. One is
 * image, another is screen. computeLayout() uses matrix to map from image
 * space to screen space.
 */
public class HighlightView {

    public static final int GROW_NONE = (1 << 0);
    public static final int GROW_LEFT_EDGE = (1 << 1);
    public static final int GROW_RIGHT_EDGE = (1 << 2);
    public static final int GROW_TOP_EDGE = (1 << 3);
    public static final int GROW_BOTTOM_EDGE = (1 << 4);
    public static final int MOVE = (1 << 5);

    private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF33B5E5;
    private static final float HANDLE_RADIUS_DP = 12f;
    private static final float OUTLINE_DP = 2f;

    enum ModifyMode {None, Move, Grow}

    enum HandleMode {Changing, Always, Never}

    RectF cropRect; // Image space
    Rect drawRect; // Screen space
    Matrix matrix;
    private RectF imageRect; // Image space

    private final Paint outsidePaint = new Paint();
    private final Paint outlinePaint = new Paint();
    private final Paint handlePaint = new Paint();

    private View viewContext; // View displaying image
    private boolean showThirds;
    private boolean showCircle;
    private int highlightColor;

    private ModifyMode modifyMode = ModifyMode.None;
    private HandleMode handleMode = HandleMode.Changing;
    private boolean maintainAspectRatio;
    private float initialAspectRatio;
    private float handleRadius;
    private float outlineWidth;
    private boolean isFocused;

    public HighlightView(View context) {
        viewContext = context;
        initStyles(context.getContext());
    }

    private void initStyles(Context context) {
        TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(R.attr.IPicker_CropImageStyle, outValue, true);
        TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.IPicker_CropImageView);
        try {
            showThirds = attributes.getBoolean(R.styleable.IPicker_CropImageView_ipicker_showThirds, false);
            showCircle = attributes.getBoolean(R.styleable.IPicker_CropImageView_ipicker_showCircle, false);
            highlightColor = attributes.getColor(R.styleable.IPicker_CropImageView_ipicker_highlightColor,
                    DEFAULT_HIGHLIGHT_COLOR);
            handleMode = HandleMode.values()[attributes.getInt(R.styleable.IPicker_CropImageView_ipicker_showHandles, 0)];
        } finally {
            attributes.recycle();
        }
    }

    public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean maintainAspectRatio) {
        matrix = new Matrix(m);

        this.cropRect = cropRect;
        this.imageRect = new RectF(imageRect);
        this.maintainAspectRatio = maintainAspectRatio;

        initialAspectRatio = this.cropRect.width() / this.cropRect.height();
        drawRect = computeLayout();

        outsidePaint.setARGB(125, 50, 50, 50);
        outlinePaint.setStyle(Paint.Style.STROKE);
        outlinePaint.setAntiAlias(true);
        outlineWidth = dpToPx(OUTLINE_DP);

        handlePaint.setColor(highlightColor);
        handlePaint.setStyle(Paint.Style.FILL);
        handlePaint.setAntiAlias(true);
        handleRadius = dpToPx(HANDLE_RADIUS_DP);

        modifyMode = ModifyMode.None;
    }

    private float dpToPx(float dp) {
        return dp * viewContext.getResources().getDisplayMetrics().density;
    }

    protected void draw(Canvas canvas) {
        canvas.save();
        Path path = new Path();
        outlinePaint.setStrokeWidth(outlineWidth);
        if (!hasFocus()) {
            outlinePaint.setColor(Color.BLACK);
            canvas.drawRect(drawRect, outlinePaint);
        } else {
            Rect viewDrawingRect = new Rect();
            viewContext.getDrawingRect(viewDrawingRect);

            path.addRect(new RectF(drawRect), Path.Direction.CW);
            outlinePaint.setColor(highlightColor);

            if (isClipPathSupported(canvas)) {
                canvas.clipPath(path, Region.Op.DIFFERENCE);
                canvas.drawRect(viewDrawingRect, outsidePaint);
            } else {
                drawOutsideFallback(canvas);
            }

            canvas.restore();
            canvas.drawPath(path, outlinePaint);

            if (showThirds) {
                drawThirds(canvas);
            }

            if (showCircle) {
                drawCircle(canvas);
            }

            if (handleMode == HandleMode.Always ||
                    (handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
                drawHandles(canvas);
            }
        }
    }

    /*
     * Fall back to naive method for darkening outside crop area
     */
    private void drawOutsideFallback(Canvas canvas) {
        canvas.drawRect(0, 0, canvas.getWidth(), drawRect.top, outsidePaint);
        canvas.drawRect(0, drawRect.bottom, canvas.getWidth(), canvas.getHeight(), outsidePaint);
        canvas.drawRect(0, drawRect.top, drawRect.left, drawRect.bottom, outsidePaint);
        canvas.drawRect(drawRect.right, drawRect.top, canvas.getWidth(), drawRect.bottom, outsidePaint);
    }

    /*
     * Clip path is broken, unreliable or not supported on:
     * - JellyBean MR1
     * - ICS & ICS MR1 with hardware acceleration turned on
     */
    @SuppressLint("NewApi")
    private boolean isClipPathSupported(Canvas canvas) {
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
            return false;
        } else if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
                || Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
            return true;
        } else {
            return !canvas.isHardwareAccelerated();
        }
    }

    private void drawHandles(Canvas canvas) {
        int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);
        int yMiddle = drawRect.top + ((drawRect.bottom - drawRect.top) / 2);

        canvas.drawCircle(drawRect.left, yMiddle, handleRadius, handlePaint);
        canvas.drawCircle(xMiddle, drawRect.top, handleRadius, handlePaint);
        canvas.drawCircle(drawRect.right, yMiddle, handleRadius, handlePaint);
        canvas.drawCircle(xMiddle, drawRect.bottom, handleRadius, handlePaint);
    }

    private void drawThirds(Canvas canvas) {
        outlinePaint.setStrokeWidth(1);
        float xThird = (drawRect.right - drawRect.left) / 3;
        float yThird = (drawRect.bottom - drawRect.top) / 3;

        canvas.drawLine(drawRect.left + xThird, drawRect.top,
                drawRect.left + xThird, drawRect.bottom, outlinePaint);
        canvas.drawLine(drawRect.left + xThird * 2, drawRect.top,
                drawRect.left + xThird * 2, drawRect.bottom, outlinePaint);
        canvas.drawLine(drawRect.left, drawRect.top + yThird,
                drawRect.right, drawRect.top + yThird, outlinePaint);
        canvas.drawLine(drawRect.left, drawRect.top + yThird * 2,
                drawRect.right, drawRect.top + yThird * 2, outlinePaint);
    }

    private void drawCircle(Canvas canvas) {
        outlinePaint.setStrokeWidth(1);
        canvas.drawOval(new RectF(drawRect), outlinePaint);
    }

    public void setMode(ModifyMode mode) {
        if (mode != modifyMode) {
            modifyMode = mode;
            viewContext.invalidate();
        }
    }

    // Determines which edges are hit by touching at (x, y)
    public int getHit(float x, float y) {
        Rect r = computeLayout();
        final float hysteresis = 20F;
        int retval = GROW_NONE;

        // verticalCheck makes sure the position is between the top and
        // the bottom edge (with some tolerance). Similar for horizCheck.
        boolean verticalCheck = (y >= r.top - hysteresis)
                && (y < r.bottom + hysteresis);
        boolean horizCheck = (x >= r.left - hysteresis)
                && (x < r.right + hysteresis);

        // Check whether the position is near some edge(s)
        if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
            retval |= GROW_LEFT_EDGE;
        }
        if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
            retval |= GROW_RIGHT_EDGE;
        }
        if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
            retval |= GROW_TOP_EDGE;
        }
        if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
            retval |= GROW_BOTTOM_EDGE;
        }

        // Not near any edge but inside the rectangle: move
        if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
            retval = MOVE;
        }
        return retval;
    }

    // Handles motion (dx, dy) in screen space.
    // The "edge" parameter specifies which edges the user is dragging.
    void handleMotion(int edge, float dx, float dy) {
        Rect r = computeLayout();
        if (edge == MOVE) {
            // Convert to image space before sending to moveBy()
            moveBy(dx * (cropRect.width() / r.width()),
                    dy * (cropRect.height() / r.height()));
        } else {
            if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
                dx = 0;
            }

            if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
                dy = 0;
            }

            // Convert to image space before sending to growBy()
            float xDelta = dx * (cropRect.width() / r.width());
            float yDelta = dy * (cropRect.height() / r.height());
            growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
                    (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
        }
    }

    // Grows the cropping rectangle by (dx, dy) in image space
    void moveBy(float dx, float dy) {
        Rect invalRect = new Rect(drawRect);

        cropRect.offset(dx, dy);

        // Put the cropping rectangle inside image rectangle
        cropRect.offset(
                Math.max(0, imageRect.left - cropRect.left),
                Math.max(0, imageRect.top - cropRect.top));

        cropRect.offset(
                Math.min(0, imageRect.right - cropRect.right),
                Math.min(0, imageRect.bottom - cropRect.bottom));

        drawRect = computeLayout();
        invalRect.union(drawRect);
        invalRect.inset(-(int) handleRadius, -(int) handleRadius);
        viewContext.invalidate(invalRect);
    }

    // Grows the cropping rectangle by (dx, dy) in image space.
    void growBy(float dx, float dy) {
        if (maintainAspectRatio) {
            if (dx != 0) {
                dy = dx / initialAspectRatio;
            } else if (dy != 0) {
                dx = dy * initialAspectRatio;
            }
        }

        // Don't let the cropping rectangle grow too fast.
        // Grow at most half of the difference between the image rectangle and
        // the cropping rectangle.
        RectF r = new RectF(cropRect);
        if (dx > 0F && r.width() + 2 * dx > imageRect.width()) {
            dx = (imageRect.width() - r.width()) / 2F;
            if (maintainAspectRatio) {
                dy = dx / initialAspectRatio;
            }
        }
        if (dy > 0F && r.height() + 2 * dy > imageRect.height()) {
            dy = (imageRect.height() - r.height()) / 2F;
            if (maintainAspectRatio) {
                dx = dy * initialAspectRatio;
            }
        }

        r.inset(-dx, -dy);

        // Don't let the cropping rectangle shrink too fast
        final float widthCap = 25F;
        if (r.width() < widthCap) {
            r.inset(-(widthCap - r.width()) / 2F, 0F);
        }
        float heightCap = maintainAspectRatio
                ? (widthCap / initialAspectRatio)
                : widthCap;
        if (r.height() < heightCap) {
            r.inset(0F, -(heightCap - r.height()) / 2F);
        }

        // Put the cropping rectangle inside the image rectangle
        if (r.left < imageRect.left) {
            r.offset(imageRect.left - r.left, 0F);
        } else if (r.right > imageRect.right) {
            r.offset(-(r.right - imageRect.right), 0F);
        }
        if (r.top < imageRect.top) {
            r.offset(0F, imageRect.top - r.top);
        } else if (r.bottom > imageRect.bottom) {
            r.offset(0F, -(r.bottom - imageRect.bottom));
        }

        cropRect.set(r);
        drawRect = computeLayout();
        viewContext.invalidate();
    }

    // Returns the cropping rectangle in image space with specified scale
    public Rect getScaledCropRect(float scale) {
        return new Rect((int) (cropRect.left * scale), (int) (cropRect.top * scale),
                (int) (cropRect.right * scale), (int) (cropRect.bottom * scale));
    }

    // Maps the cropping rectangle from image space to screen space
    private Rect computeLayout() {
        RectF r = new RectF(cropRect.left, cropRect.top,
                cropRect.right, cropRect.bottom);
        matrix.mapRect(r);
        return new Rect(Math.round(r.left), Math.round(r.top),
                Math.round(r.right), Math.round(r.bottom));
    }

    public void invalidate() {
        drawRect = computeLayout();
    }

    public boolean hasFocus() {
        return isFocused;
    }

    public void setFocus(boolean isFocused) {
        this.isFocused = isFocused;
    }

}


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

package com.liuguangqiang.ipicker.crop;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;

import androidx.appcompat.widget.AppCompatImageView;

/*
 * Modified from original in AOSP.
 */
public abstract class ImageViewTouchBase extends AppCompatImageView {

    private static final float SCALE_RATE = 1.25F;

    // This is the base transformation which is used to show the image
    // initially.  The current computation for this shows the image in
    // it's entirety, letterboxing as needed.  One could choose to
    // show the image as cropped instead.
    //
    // This matrix is recomputed when we go from the thumbnail image to
    // the full size image.
    protected Matrix baseMatrix = new Matrix();

    // This is the supplementary transformation which reflects what
    // the user has done in terms of zooming and panning.
    //
    // This matrix remains the same when we go from the thumbnail image
    // to the full size image.
    protected Matrix suppMatrix = new Matrix();

    // This is the final matrix which is computed as the concatentation
    // of the base matrix and the supplementary matrix.
    private final Matrix displayMatrix = new Matrix();

    // Temporary buffer used for getting the values out of a matrix.
    private final float[] matrixValues = new float[9];

    // The current bitmap being displayed.
    protected final RotateBitmap bitmapDisplayed = new RotateBitmap(null, 0);

    int thisWidth = -1;
    int thisHeight = -1;

    float maxZoom;

    private Runnable onLayoutRunnable;

    protected Handler handler = new Handler();

    // ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
    // its use of that Bitmap
    public interface Recycler {
        public void recycle(Bitmap b);
    }

    private Recycler recycler;

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

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

    public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public void setRecycler(Recycler recycler) {
        this.recycler = recycler;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        thisWidth = right - left;
        thisHeight = bottom - top;
        Runnable r = onLayoutRunnable;
        if (r != null) {
            onLayoutRunnable = null;
            r.run();
        }
        if (bitmapDisplayed.getBitmap() != null) {
            getProperBaseMatrix(bitmapDisplayed, baseMatrix, true);
            setImageMatrix(getImageViewMatrix());
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            event.startTracking();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
            if (getScale() > 1.0f) {
                // If we're zoomed in, pressing Back jumps out to show the
                // entire image, otherwise Back returns the user to the gallery
                zoomTo(1.0f);
                return true;
            }
        }
        return super.onKeyUp(keyCode, event);
    }

    @Override
    public void setImageBitmap(Bitmap bitmap) {
        setImageBitmap(bitmap, 0);
    }

    private void setImageBitmap(Bitmap bitmap, int rotation) {
        super.setImageBitmap(bitmap);
        Drawable d = getDrawable();
        if (d != null) {
            d.setDither(true);
        }

        Bitmap old = bitmapDisplayed.getBitmap();
        bitmapDisplayed.setBitmap(bitmap);
        bitmapDisplayed.setRotation(rotation);

        if (old != null && old != bitmap && recycler != null) {
            recycler.recycle(old);
        }
    }

    public void clear() {
        setImageBitmapResetBase(null, true);
    }


    // This function changes bitmap, reset base matrix according to the size
    // of the bitmap, and optionally reset the supplementary matrix
    public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {
        setImageRotateBitmapResetBase(new RotateBitmap(bitmap, 0), resetSupp);
    }

    public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, final boolean resetSupp) {
        final int viewWidth = getWidth();

        if (viewWidth <= 0) {
            onLayoutRunnable = new Runnable() {
                public void run() {
                    setImageRotateBitmapResetBase(bitmap, resetSupp);
                }
            };
            return;
        }

        if (bitmap.getBitmap() != null) {
            getProperBaseMatrix(bitmap, baseMatrix, true);
            setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
        } else {
            baseMatrix.reset();
            setImageBitmap(null);
        }

        if (resetSupp) {
            suppMatrix.reset();
        }
        setImageMatrix(getImageViewMatrix());
        maxZoom = calculateMaxZoom();
    }

    // Center as much as possible in one or both axis.  Centering is defined as follows:
    // * If the image is scaled down below the view's dimensions then center it.
    // * If the image is scaled larger than the view and is translated out of view then translate it back into view.
    public void center() {
        final Bitmap bitmap = bitmapDisplayed.getBitmap();
        if (bitmap == null) {
            return;
        }
        Matrix m = getImageViewMatrix();

        RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
        m.mapRect(rect);

        float height = rect.height();
        float width = rect.width();

        float deltaX = 0, deltaY = 0;

        deltaY = centerVertical(rect, height, deltaY);
        deltaX = centerHorizontal(rect, width, deltaX);

        postTranslate(deltaX, deltaY);
        setImageMatrix(getImageViewMatrix());
    }

    private float centerVertical(RectF rect, float height, float deltaY) {
        int viewHeight = getHeight();
        if (height < viewHeight) {
            deltaY = (viewHeight - height) / 2 - rect.top;
        } else if (rect.top > 0) {
            deltaY = -rect.top;
        } else if (rect.bottom < viewHeight) {
            deltaY = getHeight() - rect.bottom;
        }
        return deltaY;
    }

    private float centerHorizontal(RectF rect, float width, float deltaX) {
        int viewWidth = getWidth();
        if (width < viewWidth) {
            deltaX = (viewWidth - width) / 2 - rect.left;
        } else if (rect.left > 0) {
            deltaX = -rect.left;
        } else if (rect.right < viewWidth) {
            deltaX = viewWidth - rect.right;
        }
        return deltaX;
    }

    private void init() {
        setScaleType(ScaleType.MATRIX);
    }

    protected float getValue(Matrix matrix, int whichValue) {
        matrix.getValues(matrixValues);
        return matrixValues[whichValue];
    }

    // Get the scale factor out of the matrix.
    public float getScale(Matrix matrix) {
        return getValue(matrix, Matrix.MSCALE_X);
    }

    public float getScale() {
        return getScale(suppMatrix);
    }

    // Setup the base matrix so that the image is centered and scaled properly.
    private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, boolean includeRotation) {
        float viewWidth = getWidth();
        float viewHeight = getHeight();

        float w = bitmap.getWidth();
        float h = bitmap.getHeight();
        matrix.reset();

        // We limit up-scaling to 3x otherwise the result may look bad if it's a small icon
        float widthScale = Math.min(viewWidth / w, 3.0f);
        float heightScale = Math.min(viewHeight / h, 3.0f);
        float scale = Math.min(widthScale, heightScale);

        if (includeRotation) {
            matrix.postConcat(bitmap.getRotateMatrix());
        }
        matrix.postScale(scale, scale);
        matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
    }

    // Combine the base matrix and the supp matrix to make the final matrix
    protected Matrix getImageViewMatrix() {
        // The final matrix is computed as the concatentation of the base matrix
        // and the supplementary matrix
        displayMatrix.set(baseMatrix);
        displayMatrix.postConcat(suppMatrix);
        return displayMatrix;
    }

    public Matrix getUnrotatedMatrix() {
        Matrix unrotated = new Matrix();
        getProperBaseMatrix(bitmapDisplayed, unrotated, false);
        unrotated.postConcat(suppMatrix);
        return unrotated;
    }

    protected float calculateMaxZoom() {
        if (bitmapDisplayed.getBitmap() == null) {
            return 1F;
        }

        float fw = (float) bitmapDisplayed.getWidth() / (float) thisWidth;
        float fh = (float) bitmapDisplayed.getHeight() / (float) thisHeight;
        return Math.max(fw, fh) * 4; // 400%
    }

    protected void zoomTo(float scale, float centerX, float centerY) {
        if (scale > maxZoom) {
            scale = maxZoom;
        }

        float oldScale = getScale();
        float deltaScale = scale / oldScale;

        suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
        setImageMatrix(getImageViewMatrix());
        center();
    }

    protected void zoomTo(final float scale, final float centerX,
                          final float centerY, final float durationMs) {
        final float incrementPerMs = (scale - getScale()) / durationMs;
        final float oldScale = getScale();
        final long startTime = System.currentTimeMillis();

        handler.post(new Runnable() {
            public void run() {
                long now = System.currentTimeMillis();
                float currentMs = Math.min(durationMs, now - startTime);
                float target = oldScale + (incrementPerMs * currentMs);
                zoomTo(target, centerX, centerY);

                if (currentMs < durationMs) {
                    handler.post(this);
                }
            }
        });
    }

    protected void zoomTo(float scale) {
        float cx = getWidth() / 2F;
        float cy = getHeight() / 2F;
        zoomTo(scale, cx, cy);
    }

    protected void zoomIn() {
        zoomIn(SCALE_RATE);
    }

    protected void zoomOut() {
        zoomOut(SCALE_RATE);
    }

    protected void zoomIn(float rate) {
        if (getScale() >= maxZoom) {
            return; // Don't let the user zoom into the molecular level
        }
        if (bitmapDisplayed.getBitmap() == null) {
            return;
        }

        float cx = getWidth() / 2F;
        float cy = getHeight() / 2F;

        suppMatrix.postScale(rate, rate, cx, cy);
        setImageMatrix(getImageViewMatrix());
    }

    protected void zoomOut(float rate) {
        if (bitmapDisplayed.getBitmap() == null) {
            return;
        }

        float cx = getWidth() / 2F;
        float cy = getHeight() / 2F;

        // Zoom out to at most 1x
        Matrix tmp = new Matrix(suppMatrix);
        tmp.postScale(1F / rate, 1F / rate, cx, cy);

        if (getScale(tmp) < 1F) {
            suppMatrix.setScale(1F, 1F, cx, cy);
        } else {
            suppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
        }
        setImageMatrix(getImageViewMatrix());
        center();
    }

    protected void postTranslate(float dx, float dy) {
        suppMatrix.postTranslate(dx, dy);
    }

    protected void panBy(float dx, float dy) {
        postTranslate(dx, dy);
        setImageMatrix(getImageViewMatrix());
    }
}


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

package com.liuguangqiang.ipicker.crop;

public class Log {

    private static final String TAG = "android-crop";

    public static void e(String msg) {
        android.util.Log.e(TAG, msg);
    }

    public static void e(String msg, Throwable e) {
        android.util.Log.e(TAG, msg, e);
    }

}


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

package com.liuguangqiang.ipicker.crop;

import android.os.Bundle;

import java.util.ArrayList;

import androidx.appcompat.app.AppCompatActivity;

/*
 * Modified from original in AOSP.
 */
public abstract class MonitoredActivity extends AppCompatActivity {

    private final ArrayList<LifeCycleListener> listeners = new ArrayList<LifeCycleListener>();

    public static interface LifeCycleListener {
        public void onActivityCreated(MonitoredActivity activity);

        public void onActivityDestroyed(MonitoredActivity activity);

        public void onActivityStarted(MonitoredActivity activity);

        public void onActivityStopped(MonitoredActivity activity);
    }

    public static class LifeCycleAdapter implements LifeCycleListener {
        public void onActivityCreated(MonitoredActivity activity) {
        }

        public void onActivityDestroyed(MonitoredActivity activity) {
        }

        public void onActivityStarted(MonitoredActivity activity) {
        }

        public void onActivityStopped(MonitoredActivity activity) {
        }
    }

    public void addLifeCycleListener(LifeCycleListener listener) {
        if (listeners.contains(listener)) return;
        listeners.add(listener);
    }

    public void removeLifeCycleListener(LifeCycleListener listener) {
        listeners.remove(listener);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        for (LifeCycleListener listener : listeners) {
            listener.onActivityCreated(this);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        for (LifeCycleListener listener : listeners) {
            listener.onActivityDestroyed(this);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        for (LifeCycleListener listener : listeners) {
            listener.onActivityStarted(this);
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        for (LifeCycleListener listener : listeners) {
            listener.onActivityStopped(this);
        }
    }

}


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

package com.liuguangqiang.ipicker.crop;

import android.graphics.Bitmap;
import android.graphics.Matrix;

/*
 * Modified from original in AOSP.
 */
public class RotateBitmap {

    private Bitmap bitmap;
    private int rotation;

    public RotateBitmap(Bitmap bitmap, int rotation) {
        this.bitmap = bitmap;
        this.rotation = rotation % 360;
    }

    public void setRotation(int rotation) {
        this.rotation = rotation;
    }

    public int getRotation() {
        return rotation;
    }

    public Bitmap getBitmap() {
        return bitmap;
    }

    public void setBitmap(Bitmap bitmap) {
        this.bitmap = bitmap;
    }

    public Matrix getRotateMatrix() {
        // By default this is an identity matrix
        Matrix matrix = new Matrix();
        if (bitmap != null && rotation != 0) {
            // We want to do the rotation at origin, but since the bounding
            // rectangle will be changed after rotation, so the delta values
            // are based on old & new width/height respectively.
            int cx = bitmap.getWidth() / 2;
            int cy = bitmap.getHeight() / 2;
            matrix.preTranslate(-cx, -cy);
            matrix.postRotate(rotation);
            matrix.postTranslate(getWidth() / 2, getHeight() / 2);
        }
        return matrix;
    }

    public boolean isOrientationChanged() {
        return (rotation / 90) % 2 != 0;
    }

    public int getHeight() {
        if (bitmap == null) return 0;
        if (isOrientationChanged()) {
            return bitmap.getWidth();
        } else {
            return bitmap.getHeight();
        }
    }

    public int getWidth() {
        if (bitmap == null) return 0;
        if (isOrientationChanged()) {
            return bitmap.getHeight();
        } else {
            return bitmap.getWidth();
        }
    }

    public void recycle() {
        if (bitmap != null) {
            bitmap.recycle();
            bitmap = null;
        }
    }
}



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

package com.liuguangqiang.ipicker.entities;

/**
 * Created by Eric on 16/9/12.
 */

public class Photo {

    public String path;

    public boolean showCamera = false;

    public Photo(String path) {
        this.path = path;
        this.showCamera = false;
    }

    public Photo(boolean showCamera) {
        this.showCamera = showCamera;
    }

}


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

package com.liuguangqiang.ipicker.internal;

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

import com.liuguangqiang.ipicker.entities.Photo;

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

/**
 * A helper for querying all images from sd card.
 * <p>
 * Created by Eric on 16/9/12.
 */
public class ImageMedia {

    /**
     * Return a collection of image path.
     *
     * @param context
     * @return
     */
    public static List<Photo> queryAll(Context context) {
        List<Photo> photos = new ArrayList<>();

        // which image properties are we querying
        String[] projection = new String[]{
                MediaStore.Images.Media._ID,
                MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
                MediaStore.Images.Media.DATE_TAKEN,
                MediaStore.Images.Media.DATA,
        };

        Uri images = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        Cursor cur = MediaStore.Images.Media.query(
                context.getContentResolver(),
                images,                                             // Uri
                projection,                                         // Which columns to return
                null,                                               // Which rows to return (all rows)
                null,                                               // Selection arguments (none)
                MediaStore.Images.Media.DATE_TAKEN + " DESC"        // Ordering
        );

        if (cur.moveToFirst()) {
            int dataColumn = cur.getColumnIndex(
                    MediaStore.Images.Media.DATA);
            do {
                // Get the field values
                photos.add(new Photo(cur.getString(dataColumn)));
            } while (cur.moveToNext());
        }

        return photos;
    }

    /**
     * Return the absolutely path of a uri.
     *
     * @param context
     * @param uri
     * @return
     */
    public static String getFilePath(Context context, Uri uri) {
        String[] filePathColumn = {MediaStore.Images.Media.DATA};
        Cursor cursor = context.getContentResolver().query(uri, filePathColumn, null, null, null);
        cursor.moveToFirst();
        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
        String filePath = cursor.getString(columnIndex);
        cursor.close();
        return filePath;
    }

}


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

package com.liuguangqiang.ipicker.internal;

import android.util.Log;

/**
 * Created by Eric on 16/9/12.
 */

public class Logger {

    private static final String TAG = "IPicker";

    public static void i(String msg) {
        Log.i(TAG, msg);
    }

}


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

package com.liuguangqiang.ipicker.widgets;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

/**
 * Created by Eric on 16/9/12.
 */

public class SquareRelativeLayout extends RelativeLayout {

    public SquareRelativeLayout(Context context) {
        super(context);
    }

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

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
    }

}


================================================
FILE: library/src/main/res/drawable/crop_texture.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/crop_tile"
    android:tileMode="repeat" />

================================================
FILE: library/src/main/res/drawable/round_white.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<!--
  ~  Copyright 2015 GoIn Inc. All rights reserved.
  -->

<shape xmlns:android="http://schemas.android.com/apk/res/android">

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

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

    <size
        android:width="5dp"
        android:height="5dp" />

</shape>

================================================
FILE: library/src/main/res/layout/activity_ipicker.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background"
    android:orientation="vertical">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_photos"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

================================================
FILE: library/src/main/res/layout/activity_ipicker_crop.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="match_parent"
    android:orientation="vertical">

    <com.liuguangqiang.ipicker.crop.CropImageView
        android:id="@+id/ipicker_crop_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/crop_texture" />

</LinearLayout>

================================================
FILE: library/src/main/res/layout/item_photo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<com.liuguangqiang.ipicker.widgets.SquareRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="1dp">

    <ImageView
        android:id="@+id/iv_photo"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop" />

    <ImageView
        android:id="@+id/iv_camera"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/placeholder"
        android:scaleType="center"
        android:src="@mipmap/ic_camera"
        android:visibility="gone" />

    <ImageView
        android:id="@+id/iv_checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_margin="6dp"
        android:background="@mipmap/ic_checkbox" />

</com.liuguangqiang.ipicker.widgets.SquareRelativeLayout>

================================================
FILE: library/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<resources>

    <attr name="IPicker_CropImageStyle" format="reference" />

    <declare-styleable name="IPicker_CropImageView">
        <attr name="ipicker_highlightColor" format="reference|color" />
        <attr name="ipicker_showThirds" format="boolean" />
        <attr name="ipicker_showCircle" format="boolean" />
        <attr name="ipicker_showHandles">
            <enum name="changing" value="0" />
            <enum name="always" value="1" />
            <enum name="never" value="2" />
        </attr>
    </declare-styleable>

</resources>

================================================
FILE: library/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <color name="color_primary">#f45d64</color>
    <color name="color_primary_dark">#c34a51</color>

    <color name="placeholder">#666666</color>
    <color name="background">#3d4049</color>
    <color name="white">#ffffff</color>

    <color name="crop__button_bar">#f3f3f3</color>
    <color name="crop__button_text">#666666</color>
    <color name="crop__selector_pressed">#1a000000</color>
    <color name="crop__selector_focused">#77000000</color>

</resources>

================================================
FILE: library/src/main/res/values/dimens.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <dimen name="action_bar_size">56dp</dimen>

</resources>

================================================
FILE: library/src/main/res/values/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">

    <string name="title_act_picker">All Pictures</string>
    <string name="action_done">DONE</string>

    <string name="btn_setting">Go Setting</string>
    <string name="no_permission_camera">Sorry, No permission to take pictures.</string>
    <string name="no_permission_gallery">Sorry, No permission to access photos on your device.</string>
    <string name="format_max_size">Only %1$d photos can be selected.</string>

    <!--crop-->
    <string name="crop__title">Clipping Picture</string>
    <string name="crop__saving">Saving picture…</string>
    <string name="crop__wait">Please wait…</string>
    <string name="crop__pick_error">No image sources available</string>
    <string name="crop__done">DONE</string>
    <string name="crop__cancel" tools:ignore="ButtonCase">CANCEL</string>

</resources>


================================================
FILE: library/src/main/res/values-zh/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">

    <string name="title_act_picker">选择照片</string>
    <string name="action_done">完成</string>

    <string name="btn_setting">设置</string>
    <string name="no_permission_camera">对不起, 没有拍照的权限。</string>
    <string name="no_permission_gallery">对不起, 没有访问照片的权限。</string>
    <string name="format_max_size">最多只能选%1$d张图片哦。</string>

    <string name="crop__title">裁剪图片</string>
    <string name="crop__done">完成</string>
    <string name="crop__cancel" tools:ignore="ButtonCase">取消</string>

</resources>


================================================
FILE: library/src/test/java/com/liuguangqiang/ipicker/ExampleUnitTest.java
================================================
package com.liuguangqiang.ipicker;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

================================================
FILE: settings.gradle
================================================
include ':app', ':library'
Download .txt
gitextract_0zrh03zi/

├── .gitignore
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── liuguangqiang/
│       │               └── ipicker/
│       │                   └── sample/
│       │                       └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── liuguangqiang/
│       │   │           └── ipicker/
│       │   │               └── sample/
│       │   │                   ├── MainActivity.java
│       │   │                   └── SelectedAdapter.java
│       │   └── res/
│       │       ├── layout/
│       │       │   ├── activity_main.xml
│       │       │   └── item_selected.xml
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── strings.xml
│       │       │   └── styles.xml
│       │       └── values-w820dp/
│       │           └── dimens.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── liuguangqiang/
│                       └── ipicker/
│                           └── sample/
│                               └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   ├── gradle-mvn-push.gradle
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── liuguangqiang/
│       │               └── ipicker/
│       │                   └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── liuguangqiang/
│       │   │           └── ipicker/
│       │   │               ├── CropImageActivity.java
│       │   │               ├── IPicker.java
│       │   │               ├── IPickerActivity.java
│       │   │               ├── adapters/
│       │   │               │   ├── BaseAdapter.java
│       │   │               │   └── PhotosAdapter.java
│       │   │               ├── crop/
│       │   │               │   ├── Crop.java
│       │   │               │   ├── CropImageView.java
│       │   │               │   ├── CropUtil.java
│       │   │               │   ├── HighlightView.java
│       │   │               │   ├── ImageViewTouchBase.java
│       │   │               │   ├── Log.java
│       │   │               │   ├── MonitoredActivity.java
│       │   │               │   └── RotateBitmap.java
│       │   │               ├── entities/
│       │   │               │   └── Photo.java
│       │   │               ├── internal/
│       │   │               │   ├── ImageMedia.java
│       │   │               │   └── Logger.java
│       │   │               └── widgets/
│       │   │                   └── SquareRelativeLayout.java
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── crop_texture.xml
│       │       │   └── round_white.xml
│       │       ├── layout/
│       │       │   ├── activity_ipicker.xml
│       │       │   ├── activity_ipicker_crop.xml
│       │       │   └── item_photo.xml
│       │       ├── values/
│       │       │   ├── attrs.xml
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   └── strings.xml
│       │       └── values-zh/
│       │           └── strings.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── liuguangqiang/
│                       └── ipicker/
│                           └── ExampleUnitTest.java
└── settings.gradle
Download .txt
SYMBOL INDEX (260 symbols across 23 files)

FILE: app/src/androidTest/java/com/liuguangqiang/ipicker/sample/ExampleInstrumentedTest.java
  class ExampleInstrumentedTest (line 17) | @RunWith(AndroidJUnit4.class)
    method useAppContext (line 19) | @Test

FILE: app/src/main/java/com/liuguangqiang/ipicker/sample/MainActivity.java
  class MainActivity (line 35) | public class MainActivity extends AppCompatActivity {
    method onCreate (line 41) | @Override
    method initViews (line 48) | private void initViews() {

FILE: app/src/main/java/com/liuguangqiang/ipicker/sample/SelectedAdapter.java
  class SelectedAdapter (line 34) | public class SelectedAdapter extends BaseAdapter<String, SelectedAdapter...
    method SelectedAdapter (line 36) | public SelectedAdapter(Context context, List<String> data) {
    method onCreateViewHolder (line 40) | @Override
    method onBindViewHolder (line 45) | @Override
    class ViewHolder (line 51) | public static class ViewHolder extends RecyclerView.ViewHolder {
      method ViewHolder (line 55) | public ViewHolder(LayoutInflater layoutInflater, ViewGroup parent, b...
      method bindData (line 60) | public void bindData(String path) {

FILE: app/src/test/java/com/liuguangqiang/ipicker/sample/ExampleUnitTest.java
  class ExampleUnitTest (line 12) | public class ExampleUnitTest {
    method addition_isCorrect (line 13) | @Test

FILE: library/src/androidTest/java/com/liuguangqiang/ipicker/ExampleInstrumentedTest.java
  class ExampleInstrumentedTest (line 17) | @RunWith(AndroidJUnit4.class)
    method useAppContext (line 19) | @Test

FILE: library/src/main/java/com/liuguangqiang/ipicker/CropImageActivity.java
  class CropImageActivity (line 55) | public class CropImageActivity extends MonitoredActivity {
    method onCreate (line 83) | @Override
    method onCreateOptionsMenu (line 99) | @Override
    method onOptionsItemSelected (line 105) | @Override
    method initToolbar (line 119) | private void initToolbar() {
    method setupWindowFlags (line 127) | @TargetApi(Build.VERSION_CODES.KITKAT)
    method setupViews (line 135) | private void setupViews() {
    method loadInput (line 149) | private void loadInput() {
    method calculateBitmapSampleSize (line 185) | private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {
    method getMaxImageSize (line 204) | private int getMaxImageSize() {
    method getMaxTextureSize (line 213) | private int getMaxTextureSize() {
    method startCrop (line 220) | private void startCrop() {
    class Cropper (line 248) | private class Cropper {
      method makeDefault (line 250) | private void makeDefault() {
      method crop (line 282) | public void crop() {
    method onSaveClicked (line 296) | private void onSaveClicked() {
    method saveImage (line 336) | private void saveImage(Bitmap croppedImage) {
    method decodeRegionCrop (line 351) | private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
    method clearImageView (line 401) | private void clearImageView() {
    method saveOutput (line 409) | private void saveOutput(Bitmap croppedImage) {
    method onDestroy (line 445) | @Override
    method onSearchRequested (line 453) | @Override
    method isSaving (line 458) | public boolean isSaving() {
    method setResultUri (line 462) | private void setResultUri(Uri uri) {
    method setResultException (line 466) | private void setResultException(Throwable throwable) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/IPicker.java
  class IPicker (line 30) | public class IPicker {
    method IPicker (line 41) | private IPicker() {
    method setLimit (line 49) | public static void setLimit(int limit) {
    method setCropEnable (line 53) | public static void setCropEnable(boolean cropEnable) {
    method setOnSelectedListener (line 57) | public static void setOnSelectedListener(OnSelectedListener listener) {
    method finish (line 66) | public static void finish(String path) {
    method finish (line 77) | public static void finish(List<String> paths) {
    method open (line 88) | public static void open(Context context) {
    method open (line 98) | public static void open(Context context, ArrayList<String> selected) {
    type OnSelectedListener (line 115) | public interface OnSelectedListener {
      method onSelected (line 117) | void onSelected(List<String> paths);

FILE: library/src/main/java/com/liuguangqiang/ipicker/IPickerActivity.java
  class IPickerActivity (line 56) | public class IPickerActivity extends AppCompatActivity implements BaseAd...
    method onCreate (line 72) | @Override
    method onOptionsItemSelected (line 82) | @Override
    method onCreateOptionsMenu (line 96) | @Override
    method onPrepareOptionsMenu (line 102) | @Override
    method initToolbar (line 108) | private void initToolbar() {
    method initViews (line 116) | private void initViews() {
    method getArguments (line 126) | private void getArguments() {
    method isSingleSelection (line 147) | private boolean isSingleSelection() {
    method onItemClick (line 151) | @Override
    method removeSelected (line 169) | private void removeSelected(int position) {
    method addSelected (line 175) | private void addSelected(int position) {
    method updateTitle (line 185) | private void updateTitle() {
    method requestPhotos (line 195) | private void requestPhotos() {
    method requestCamera (line 199) | private void requestCamera() {
    method onRequestPermissionsResult (line 203) | @Override
    method galleryGranted (line 209) | @PermissionGranted(permission = Manifest.permission.READ_EXTERNAL_STOR...
    method galleryDenied (line 214) | @PermissionDenied(permission = Manifest.permission.READ_EXTERNAL_STORAGE)
    method cameraGranted (line 219) | @PermissionGranted(permission = Manifest.permission.WRITE_EXTERNAL_STO...
    method cameraDenied (line 224) | @PermissionDenied(permission = Manifest.permission.WRITE_EXTERNAL_STOR...
    method getPhotos (line 229) | private void getPhotos() {
    method deleteTemp (line 237) | private void deleteTemp() {
    method takePicture (line 243) | private void takePicture() {
    method onActivityResult (line 260) | @Override
    method promptNoPermission (line 298) | private void promptNoPermission(@StringRes int res) {
    method cropImage (line 309) | private void cropImage(Uri source) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/adapters/BaseAdapter.java
  class BaseAdapter (line 32) | public abstract class BaseAdapter<T, VH extends RecyclerView.ViewHolder>...
    method setOnItemClickListener (line 41) | public void setOnItemClickListener(OnItemClickListener onItemClickList...
    method setOnItemLongClickListener (line 45) | public void setOnItemLongClickListener(OnItemLongClickListener onItemL...
    method BaseAdapter (line 49) | public BaseAdapter(Context context, List<T> data) {
    method getLayoutInflater (line 55) | public LayoutInflater getLayoutInflater() {
    method getItemCount (line 59) | @Override
    method onBindViewHolder (line 64) | @Override
    method onCreateViewHolder (line 88) | @Override
    method onViewRecycled (line 93) | @Override
    type OnItemClickListener (line 98) | public interface OnItemClickListener {
      method onItemClick (line 100) | void onItemClick(View view, int position);
    type OnItemLongClickListener (line 104) | public interface OnItemLongClickListener {
      method onItemLongClick (line 106) | void onItemLongClick(int position);

FILE: library/src/main/java/com/liuguangqiang/ipicker/adapters/PhotosAdapter.java
  class PhotosAdapter (line 37) | public class PhotosAdapter extends BaseAdapter<Photo, PhotosAdapter.View...
    method PhotosAdapter (line 42) | public PhotosAdapter(Context context, List<Photo> data) {
    method setSingleSelection (line 46) | public void setSingleSelection(boolean singleSelection) {
    method addSelected (line 50) | public void addSelected(List<String> list) {
    method addSelected (line 54) | public void addSelected(Photo photo) {
    method removeSelected (line 58) | public void removeSelected(Photo photo) {
    method getSelected (line 62) | public List<String> getSelected() {
    method isSelected (line 66) | public boolean isSelected(String path) {
    method getSelectedTotal (line 70) | public int getSelectedTotal() {
    method onCreateViewHolder (line 74) | @Override
    method onBindViewHolder (line 79) | @Override
    class ViewHolder (line 85) | public static class ViewHolder extends RecyclerView.ViewHolder {
      method ViewHolder (line 91) | public ViewHolder(LayoutInflater layoutInflater, ViewGroup parent, b...
      method bindData (line 98) | public void bindData(Photo entity, boolean selected) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/crop/Crop.java
  class Crop (line 36) | public class Crop {
    type Extra (line 42) | public interface Extra {
    method of (line 59) | public static Crop of(Uri source, Uri destination) {
    method Crop (line 63) | private Crop(Uri source, Uri destination) {
    method withAspect (line 75) | public Crop withAspect(int x, int y) {
    method asSquare (line 84) | public Crop asSquare() {
    method withMaxSize (line 96) | public Crop withMaxSize(int width, int height) {
    method asPng (line 107) | public Crop asPng(boolean asPng) {
    method start (line 117) | public void start(Activity activity) {
    method start (line 127) | public void start(Activity activity, int requestCode) {
    method start (line 137) | public void start(Context context, Fragment fragment) {
    method start (line 148) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    method getIntent (line 159) | public Intent getIntent(Context context) {
    method getOutput (line 169) | public static Uri getOutput(Intent result) {
    method getError (line 179) | public static Throwable getError(Intent result) {
    method pickImage (line 188) | public static void pickImage(Activity activity) {
    method pickImage (line 198) | public static void pickImage(Context context, Fragment fragment) {
    method pickImage (line 208) | public static void pickImage(Activity activity, int requestCode) {
    method pickImage (line 223) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    method getImagePicker (line 232) | private static Intent getImagePicker() {
    method showImagePickerError (line 236) | private static void showImagePickerError(Context context) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/crop/CropImageView.java
  class CropImageView (line 31) | public class CropImageView extends ImageViewTouchBase {
    method CropImageView (line 42) | public CropImageView(Context context) {
    method CropImageView (line 46) | public CropImageView(Context context, AttributeSet attrs) {
    method CropImageView (line 50) | public CropImageView(Context context, AttributeSet attrs, int defStyle) {
    method onLayout (line 54) | @Override
    method zoomTo (line 69) | @Override
    method zoomIn (line 78) | @Override
    method zoomOut (line 87) | @Override
    method postTranslate (line 96) | @Override
    method onTouchEvent (line 105) | @Override
    method ensureVisible (line 158) | private void ensureVisible(HighlightView hv) {
    method centerBasedOnHighlightView (line 177) | private void centerBasedOnHighlightView(HighlightView hv) {
    method onDraw (line 202) | @Override
    method add (line 210) | public void add(HighlightView hv) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/crop/CropUtil.java
  class CropUtil (line 42) | public class CropUtil {
    method closeSilently (line 47) | public static void closeSilently(@Nullable Closeable c) {
    method getExifRotation (line 56) | public static int getExifRotation(File imageFile) {
    method copyExifRotation (line 77) | public static boolean copyExifRotation(File sourceFile, File destFile) {
    method getFromMediaUri (line 91) | @Nullable
    method getTempFilename (line 126) | private static String getTempFilename(Context context) throws IOExcept...
    method getFromMediaUriPfd (line 132) | @Nullable
    method startBackgroundJob (line 161) | public static void startBackgroundJob(MonitoredActivity activity,
    class BackgroundJob (line 170) | private static class BackgroundJob extends MonitoredActivity.LifeCycle...
      method run (line 177) | public void run() {
      method BackgroundJob (line 183) | public BackgroundJob(MonitoredActivity activity, Runnable job,
      method run (line 192) | public void run() {
      method onActivityDestroyed (line 200) | @Override
      method onActivityStopped (line 208) | @Override
      method onActivityStarted (line 213) | @Override

FILE: library/src/main/java/com/liuguangqiang/ipicker/crop/HighlightView.java
  class HighlightView (line 44) | public class HighlightView {
    type ModifyMode (line 57) | enum ModifyMode {None, Move, Grow}
    type HandleMode (line 59) | enum HandleMode {Changing, Always, Never}
    method HighlightView (line 83) | public HighlightView(View context) {
    method initStyles (line 88) | private void initStyles(Context context) {
    method setup (line 103) | public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean ma...
    method dpToPx (line 126) | private float dpToPx(float dp) {
    method draw (line 130) | protected void draw(Canvas canvas) {
    method drawOutsideFallback (line 172) | private void drawOutsideFallback(Canvas canvas) {
    method isClipPathSupported (line 184) | @SuppressLint("NewApi")
    method drawHandles (line 196) | private void drawHandles(Canvas canvas) {
    method drawThirds (line 206) | private void drawThirds(Canvas canvas) {
    method drawCircle (line 221) | private void drawCircle(Canvas canvas) {
    method setMode (line 226) | public void setMode(ModifyMode mode) {
    method getHit (line 234) | public int getHit(float x, float y) {
    method handleMotion (line 269) | void handleMotion(int edge, float dx, float dy) {
    method moveBy (line 293) | void moveBy(float dx, float dy) {
    method growBy (line 314) | void growBy(float dx, float dy) {
    method getScaledCropRect (line 372) | public Rect getScaledCropRect(float scale) {
    method computeLayout (line 378) | private Rect computeLayout() {
    method invalidate (line 386) | public void invalidate() {
    method hasFocus (line 390) | public boolean hasFocus() {
    method setFocus (line 394) | public void setFocus(boolean isFocused) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/crop/ImageViewTouchBase.java
  class ImageViewTouchBase (line 33) | public abstract class ImageViewTouchBase extends AppCompatImageView {
    type Recycler (line 74) | public interface Recycler {
      method recycle (line 75) | public void recycle(Bitmap b);
    method ImageViewTouchBase (line 80) | public ImageViewTouchBase(Context context) {
    method ImageViewTouchBase (line 85) | public ImageViewTouchBase(Context context, AttributeSet attrs) {
    method ImageViewTouchBase (line 90) | public ImageViewTouchBase(Context context, AttributeSet attrs, int def...
    method setRecycler (line 95) | public void setRecycler(Recycler recycler) {
    method onLayout (line 99) | @Override
    method onKeyDown (line 115) | @Override
    method onKeyUp (line 124) | @Override
    method setImageBitmap (line 137) | @Override
    method setImageBitmap (line 142) | private void setImageBitmap(Bitmap bitmap, int rotation) {
    method clear (line 158) | public void clear() {
    method setImageBitmapResetBase (line 165) | public void setImageBitmapResetBase(final Bitmap bitmap, final boolean...
    method setImageRotateBitmapResetBase (line 169) | public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, f...
    method center (line 199) | public void center() {
    method centerVertical (line 221) | private float centerVertical(RectF rect, float height, float deltaY) {
    method centerHorizontal (line 233) | private float centerHorizontal(RectF rect, float width, float deltaX) {
    method init (line 245) | private void init() {
    method getValue (line 249) | protected float getValue(Matrix matrix, int whichValue) {
    method getScale (line 255) | public float getScale(Matrix matrix) {
    method getScale (line 259) | public float getScale() {
    method getProperBaseMatrix (line 264) | private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, b...
    method getImageViewMatrix (line 285) | protected Matrix getImageViewMatrix() {
    method getUnrotatedMatrix (line 293) | public Matrix getUnrotatedMatrix() {
    method calculateMaxZoom (line 300) | protected float calculateMaxZoom() {
    method zoomTo (line 310) | protected void zoomTo(float scale, float centerX, float centerY) {
    method zoomTo (line 323) | protected void zoomTo(final float scale, final float centerX,
    method zoomTo (line 343) | protected void zoomTo(float scale) {
    method zoomIn (line 349) | protected void zoomIn() {
    method zoomOut (line 353) | protected void zoomOut() {
    method zoomIn (line 357) | protected void zoomIn(float rate) {
    method zoomOut (line 372) | protected void zoomOut(float rate) {
    method postTranslate (line 393) | protected void postTranslate(float dx, float dy) {
    method panBy (line 397) | protected void panBy(float dx, float dy) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/crop/Log.java
  class Log (line 19) | public class Log {
    method e (line 23) | public static void e(String msg) {
    method e (line 27) | public static void e(String msg, Throwable e) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/crop/MonitoredActivity.java
  class MonitoredActivity (line 28) | public abstract class MonitoredActivity extends AppCompatActivity {
    type LifeCycleListener (line 32) | public static interface LifeCycleListener {
      method onActivityCreated (line 33) | public void onActivityCreated(MonitoredActivity activity);
      method onActivityDestroyed (line 35) | public void onActivityDestroyed(MonitoredActivity activity);
      method onActivityStarted (line 37) | public void onActivityStarted(MonitoredActivity activity);
      method onActivityStopped (line 39) | public void onActivityStopped(MonitoredActivity activity);
    class LifeCycleAdapter (line 42) | public static class LifeCycleAdapter implements LifeCycleListener {
      method onActivityCreated (line 43) | public void onActivityCreated(MonitoredActivity activity) {
      method onActivityDestroyed (line 46) | public void onActivityDestroyed(MonitoredActivity activity) {
      method onActivityStarted (line 49) | public void onActivityStarted(MonitoredActivity activity) {
      method onActivityStopped (line 52) | public void onActivityStopped(MonitoredActivity activity) {
    method addLifeCycleListener (line 56) | public void addLifeCycleListener(LifeCycleListener listener) {
    method removeLifeCycleListener (line 61) | public void removeLifeCycleListener(LifeCycleListener listener) {
    method onCreate (line 65) | @Override
    method onDestroy (line 73) | @Override
    method onStart (line 81) | @Override
    method onStop (line 89) | @Override

FILE: library/src/main/java/com/liuguangqiang/ipicker/crop/RotateBitmap.java
  class RotateBitmap (line 25) | public class RotateBitmap {
    method RotateBitmap (line 30) | public RotateBitmap(Bitmap bitmap, int rotation) {
    method setRotation (line 35) | public void setRotation(int rotation) {
    method getRotation (line 39) | public int getRotation() {
    method getBitmap (line 43) | public Bitmap getBitmap() {
    method setBitmap (line 47) | public void setBitmap(Bitmap bitmap) {
    method getRotateMatrix (line 51) | public Matrix getRotateMatrix() {
    method isOrientationChanged (line 67) | public boolean isOrientationChanged() {
    method getHeight (line 71) | public int getHeight() {
    method getWidth (line 80) | public int getWidth() {
    method recycle (line 89) | public void recycle() {

FILE: library/src/main/java/com/liuguangqiang/ipicker/entities/Photo.java
  class Photo (line 23) | public class Photo {
    method Photo (line 29) | public Photo(String path) {
    method Photo (line 34) | public Photo(boolean showCamera) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/internal/ImageMedia.java
  class ImageMedia (line 34) | public class ImageMedia {
    method queryAll (line 42) | public static List<Photo> queryAll(Context context) {
    method getFilePath (line 82) | public static String getFilePath(Context context, Uri uri) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/internal/Logger.java
  class Logger (line 25) | public class Logger {
    method i (line 29) | public static void i(String msg) {

FILE: library/src/main/java/com/liuguangqiang/ipicker/widgets/SquareRelativeLayout.java
  class SquareRelativeLayout (line 29) | public class SquareRelativeLayout extends RelativeLayout {
    method SquareRelativeLayout (line 31) | public SquareRelativeLayout(Context context) {
    method SquareRelativeLayout (line 35) | public SquareRelativeLayout(Context context, AttributeSet attrs) {
    method SquareRelativeLayout (line 39) | public SquareRelativeLayout(Context context, AttributeSet attrs, int d...
    method SquareRelativeLayout (line 43) | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    method onMeasure (line 48) | @Override

FILE: library/src/test/java/com/liuguangqiang/ipicker/ExampleUnitTest.java
  class ExampleUnitTest (line 12) | public class ExampleUnitTest {
    method addition_isCorrect (line 13) | @Test
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (144K chars).
[
  {
    "path": ".gitignore",
    "chars": 420,
    "preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbi"
  },
  {
    "path": "README.md",
    "chars": 1953,
    "preview": "IPicker\n======================================\nA material design style pictures selector.\n\n## Screenshot\n<img src=\"arts/"
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 1081,
    "preview": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 28\n    buildToolsVersion \"28.0.0\"\n    defaultCo"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 658,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "app/src/androidTest/java/com/liuguangqiang/ipicker/sample/ExampleInstrumentedTest.java",
    "chars": 768,
    "preview": "package com.liuguangqiang.ipicker.sample;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRe"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1023,
    "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/liuguangqiang/ipicker/sample/MainActivity.java",
    "chars": 2325,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "app/src/main/java/com/liuguangqiang/ipicker/sample/SelectedAdapter.java",
    "chars": 2016,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 780,
    "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/layout/item_selected.xml",
    "chars": 476,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.liuguangqiang.ipicker.widgets.SquareRelativeLayout xmlns:android=\"http://sch"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 208,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 211,
    "preview": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 70,
    "preview": "<resources>\n    <string name=\"app_name\">IPicker</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 384,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "chars": 358,
    "preview": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as s"
  },
  {
    "path": "app/src/test/java/com/liuguangqiang/ipicker/sample/ExampleUnitTest.java",
    "chars": 410,
    "preview": "package com.liuguangqiang.ipicker.sample;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example loc"
  },
  {
    "path": "build.gradle",
    "chars": 601,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    r"
  },
  {
    "path": "gradle/gradle-mvn-push.gradle",
    "chars": 3041,
    "preview": "apply plugin: 'maven'\napply plugin: 'signing'\n\ndef isReleaseBuild() {\n    return VERSION_NAME.contains(\"SNAPSHOT\") == fa"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 232,
    "preview": "#Wed Apr 08 10:04:11 CST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 1403,
    "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": "library/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "library/build.gradle",
    "chars": 1395,
    "preview": "apply plugin: 'com.android.library'\napply plugin: 'com.novoda.bintray-release'\napply from: rootProject.file('gradle/grad"
  },
  {
    "path": "library/proguard-rules.pro",
    "chars": 658,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "library/src/androidTest/java/com/liuguangqiang/ipicker/ExampleInstrumentedTest.java",
    "chars": 759,
    "preview": "package com.liuguangqiang.ipicker;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;"
  },
  {
    "path": "library/src/main/AndroidManifest.xml",
    "chars": 881,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/CropImageActivity.java",
    "chars": 15886,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/IPicker.java",
    "chars": 3070,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/IPickerActivity.java",
    "chars": 11226,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/adapters/BaseAdapter.java",
    "chars": 3190,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/adapters/PhotosAdapter.java",
    "chars": 3537,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/Crop.java",
    "chars": 6987,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/CropImageView.java",
    "chars": 7155,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/CropUtil.java",
    "chars": 8059,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/HighlightView.java",
    "chars": 14537,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/ImageViewTouchBase.java",
    "chars": 12749,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/Log.java",
    "chars": 894,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/MonitoredActivity.java",
    "chars": 2743,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/RotateBitmap.java",
    "chars": 2572,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/entities/Photo.java",
    "chars": 948,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/internal/ImageMedia.java",
    "chars": 3036,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/internal/Logger.java",
    "chars": 849,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/widgets/SquareRelativeLayout.java",
    "chars": 1611,
    "preview": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use t"
  },
  {
    "path": "library/src/main/res/drawable/crop_texture.xml",
    "chars": 177,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:s"
  },
  {
    "path": "library/src/main/res/drawable/round_white.xml",
    "chars": 342,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n  ~  Copyright 2015 GoIn Inc. All rights reserved.\n  -->\n\n<shape xmlns:andr"
  },
  {
    "path": "library/src/main/res/layout/activity_ipicker.xml",
    "chars": 508,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    a"
  },
  {
    "path": "library/src/main/res/layout/activity_ipicker_crop.xml",
    "chars": 486,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    and"
  },
  {
    "path": "library/src/main/res/layout/item_photo.xml",
    "chars": 1081,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.liuguangqiang.ipicker.widgets.SquareRelativeLayout xmlns:android=\"http://sch"
  },
  {
    "path": "library/src/main/res/values/attrs.xml",
    "chars": 593,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n\n    <attr name=\"IPicker_CropImageStyle\" format=\"reference\" />\n\n    "
  },
  {
    "path": "library/src/main/res/values/colors.xml",
    "chars": 520,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <color name=\"color_primary\">#f45d64</color>\n    <color name=\"col"
  },
  {
    "path": "library/src/main/res/values/dimens.xml",
    "chars": 112,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <dimen name=\"action_bar_size\">56dp</dimen>\n\n</resources>"
  },
  {
    "path": "library/src/main/res/values/strings.xml",
    "chars": 872,
    "preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"title_act_picker\">All Pictures</string>\n  "
  },
  {
    "path": "library/src/main/res/values-zh/strings.xml",
    "chars": 557,
    "preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"title_act_picker\">选择照片</string>\n    <strin"
  },
  {
    "path": "library/src/test/java/com/liuguangqiang/ipicker/ExampleUnitTest.java",
    "chars": 403,
    "preview": "package com.liuguangqiang.ipicker;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit"
  },
  {
    "path": "settings.gradle",
    "chars": 27,
    "preview": "include ':app', ':library'\n"
  }
]

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

About this extraction

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